Merge branch 'release-candidate' into release
This commit is contained in:
		
						commit
						ce74a35066
					
				
							
								
								
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -11,7 +11,7 @@ on: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f6 f7 | ||||
|   DEFAULT_TARGET: f6 | ||||
|   DEFAULT_TARGET: f7 | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
|  | ||||
							
								
								
									
										9
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -7,3 +7,12 @@ | ||||
| [submodule "lib/littlefs"] | ||||
| 	path = lib/littlefs | ||||
| 	url = https://github.com/littlefs-project/littlefs.git | ||||
| [submodule "lib/nanopb"] | ||||
| 	path = lib/nanopb | ||||
| 	url = https://github.com/nanopb/nanopb.git | ||||
| [submodule "assets/protobuf"] | ||||
| 	path = assets/protobuf | ||||
| 	url = https://github.com/flipperdevices/flipperzero-protobuf.git | ||||
| [submodule "lib/libusb_stm32"] | ||||
| 	path = lib/libusb_stm32 | ||||
| 	url = https://github.com/flipperdevices/libusb_stm32.git | ||||
|  | ||||
							
								
								
									
										128
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								CODE_OF_CONDUCT.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| # Contributor Covenant Code of Conduct | ||||
| 
 | ||||
| ## Our Pledge | ||||
| 
 | ||||
| We as members, contributors, and leaders pledge to make participation in our | ||||
| community a harassment-free experience for everyone, regardless of age, body | ||||
| size, visible or invisible disability, ethnicity, sex characteristics, gender | ||||
| identity and expression, level of experience, education, socio-economic status, | ||||
| nationality, personal appearance, race, religion, or sexual identity | ||||
| and orientation. | ||||
| 
 | ||||
| We pledge to act and interact in ways that contribute to an open, welcoming, | ||||
| diverse, inclusive, and healthy community. | ||||
| 
 | ||||
| ## Our Standards | ||||
| 
 | ||||
| Examples of behavior that contributes to a positive environment for our | ||||
| community include: | ||||
| 
 | ||||
| * Demonstrating empathy and kindness toward other people | ||||
| * Being respectful of differing opinions, viewpoints, and experiences | ||||
| * Giving and gracefully accepting constructive feedback | ||||
| * Accepting responsibility and apologizing to those affected by our mistakes, | ||||
|   and learning from the experience | ||||
| * Focusing on what is best not just for us as individuals, but for the | ||||
|   overall community | ||||
| 
 | ||||
| Examples of unacceptable behavior include: | ||||
| 
 | ||||
| * The use of sexualized language or imagery, and sexual attention or | ||||
|   advances of any kind | ||||
| * Trolling, insulting or derogatory comments, and personal or political attacks | ||||
| * Public or private harassment | ||||
| * Publishing others' private information, such as a physical or email | ||||
|   address, without their explicit permission | ||||
| * Other conduct which could reasonably be considered inappropriate in a | ||||
|   professional setting | ||||
| 
 | ||||
| ## Enforcement Responsibilities | ||||
| 
 | ||||
| Community leaders are responsible for clarifying and enforcing our standards of | ||||
| acceptable behavior and will take appropriate and fair corrective action in | ||||
| response to any behavior that they deem inappropriate, threatening, offensive, | ||||
| or harmful. | ||||
| 
 | ||||
| Community leaders have the right and responsibility to remove, edit, or reject | ||||
| comments, commits, code, wiki edits, issues, and other contributions that are | ||||
| not aligned to this Code of Conduct, and will communicate reasons for moderation | ||||
| decisions when appropriate. | ||||
| 
 | ||||
| ## Scope | ||||
| 
 | ||||
| This Code of Conduct applies within all community spaces, and also applies when | ||||
| an individual is officially representing the community in public spaces. | ||||
| Examples of representing our community include using an official e-mail address, | ||||
| posting via an official social media account, or acting as an appointed | ||||
| representative at an online or offline event. | ||||
| 
 | ||||
| ## Enforcement | ||||
| 
 | ||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be | ||||
| reported to the community leaders responsible for enforcement at | ||||
| hello@flipperdevices.com. | ||||
| All complaints will be reviewed and investigated promptly and fairly. | ||||
| 
 | ||||
| All community leaders are obligated to respect the privacy and security of the | ||||
| reporter of any incident. | ||||
| 
 | ||||
| ## Enforcement Guidelines | ||||
| 
 | ||||
| Community leaders will follow these Community Impact Guidelines in determining | ||||
| the consequences for any action they deem in violation of this Code of Conduct: | ||||
| 
 | ||||
| ### 1. Correction | ||||
| 
 | ||||
| **Community Impact**: Use of inappropriate language or other behavior deemed | ||||
| unprofessional or unwelcome in the community. | ||||
| 
 | ||||
| **Consequence**: A private, written warning from community leaders, providing | ||||
| clarity around the nature of the violation and an explanation of why the | ||||
| behavior was inappropriate. A public apology may be requested. | ||||
| 
 | ||||
| ### 2. Warning | ||||
| 
 | ||||
| **Community Impact**: A violation through a single incident or series | ||||
| of actions. | ||||
| 
 | ||||
| **Consequence**: A warning with consequences for continued behavior. No | ||||
| interaction with the people involved, including unsolicited interaction with | ||||
| those enforcing the Code of Conduct, for a specified period of time. This | ||||
| includes avoiding interactions in community spaces as well as external channels | ||||
| like social media. Violating these terms may lead to a temporary or | ||||
| permanent ban. | ||||
| 
 | ||||
| ### 3. Temporary Ban | ||||
| 
 | ||||
| **Community Impact**: A serious violation of community standards, including | ||||
| sustained inappropriate behavior. | ||||
| 
 | ||||
| **Consequence**: A temporary ban from any sort of interaction or public | ||||
| communication with the community for a specified period of time. No public or | ||||
| private interaction with the people involved, including unsolicited interaction | ||||
| with those enforcing the Code of Conduct, is allowed during this period. | ||||
| Violating these terms may lead to a permanent ban. | ||||
| 
 | ||||
| ### 4. Permanent Ban | ||||
| 
 | ||||
| **Community Impact**: Demonstrating a pattern of violation of community | ||||
| standards, including sustained inappropriate behavior,  harassment of an | ||||
| individual, or aggression toward or disparagement of classes of individuals. | ||||
| 
 | ||||
| **Consequence**: A permanent ban from any sort of public interaction within | ||||
| the community. | ||||
| 
 | ||||
| ## Attribution | ||||
| 
 | ||||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], | ||||
| version 2.0, available at | ||||
| https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. | ||||
| 
 | ||||
| Community Impact Guidelines were inspired by [Mozilla's code of conduct | ||||
| enforcement ladder](https://github.com/mozilla/diversity). | ||||
| 
 | ||||
| [homepage]: https://www.contributor-covenant.org | ||||
| 
 | ||||
| For answers to common questions about this code of conduct, see the FAQ at | ||||
| https://www.contributor-covenant.org/faq. Translations are available at | ||||
| https://www.contributor-covenant.org/translations. | ||||
							
								
								
									
										71
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| # Welcome to FlipperZero contributing guide <!-- omit in toc --> | ||||
| 
 | ||||
| Thank you for investing your time in contributing to our project!  | ||||
| 
 | ||||
| Read our [Code of Coduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable. | ||||
| 
 | ||||
| In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR. | ||||
| 
 | ||||
| ## New contributor guide | ||||
| 
 | ||||
| See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some helpful resources to get you comfortable with open source contribution: | ||||
| 
 | ||||
| - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github) | ||||
| - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git) | ||||
| - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) | ||||
| - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests) | ||||
| 
 | ||||
| ## Getting started | ||||
| 
 | ||||
| Before writing code and creating PR make sure that it aligns with our mission and guidlines: | ||||
| 
 | ||||
| - All our devices are intended for research and education. | ||||
| - PR that contains code intended to commit crimes is not going to be accepted. | ||||
| - 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 review by code owner. | ||||
| 
 | ||||
| Feel free to ask questions in issues if you're not sure. | ||||
| 
 | ||||
| ### Issues | ||||
| 
 | ||||
| #### Create a new issue | ||||
| 
 | ||||
| If you found a problem, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/flipperdevices/flipperzero-firmware/issues/new/choose).  | ||||
| 
 | ||||
| #### Solve an issue | ||||
| 
 | ||||
| Scan through our [existing issues](https://github.com/flipperdevices/flipperzero-firmware/issues) to find one that interests you. | ||||
| 
 | ||||
| ### Make Changes | ||||
| 
 | ||||
| 1. Fork the repository. | ||||
| - Using GitHub Desktop: | ||||
|   - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop. | ||||
|   - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)! | ||||
| 
 | ||||
| - Using the command line: | ||||
|   - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. | ||||
| 
 | ||||
| 2. Install build requirements | ||||
| 
 | ||||
| 3. Create a working branch and start with your changes! | ||||
| 
 | ||||
| ### Commit your update | ||||
| 
 | ||||
| Commit the changes once you are happy with them. Make sure that code compilation is not broken and passes tests. Check syntax and formatting. | ||||
| 
 | ||||
| ### Pull Request | ||||
| 
 | ||||
| When you're done making the changes, open a pull request, often referred to as a PR.  | ||||
| - Fill out the "Ready for review" template so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request.  | ||||
| - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one. | ||||
| - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge. | ||||
| Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information. | ||||
| - We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. | ||||
| - As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). | ||||
| - If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues. | ||||
| 
 | ||||
| ### Your PR is merged! | ||||
| 
 | ||||
| Congratulations :tada::tada: The FlipperDevices team thanks you :sparkles:. | ||||
| @ -162,6 +162,7 @@ Finally, you will have **`firmware/.obj/f6/full.dfu`** file that can be distribu | ||||
| - 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 | ||||
|  | ||||
							
								
								
									
										49
									
								
								applications/applications.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										49
									
								
								applications/applications.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -2,6 +2,7 @@ | ||||
| #include <assets_icons.h> | ||||
| 
 | ||||
| // Services
 | ||||
| extern int32_t rpc_srv(void* p); | ||||
| extern int32_t bt_srv(void* p); | ||||
| extern int32_t cli_srv(void* p); | ||||
| extern int32_t dialogs_srv(void* p); | ||||
| @ -19,8 +20,8 @@ extern int32_t desktop_srv(void* p); | ||||
| extern int32_t accessor_app(void* p); | ||||
| extern int32_t archive_app(void* p); | ||||
| extern int32_t blink_test_app(void* p); | ||||
| extern int32_t flipper_test_app(void* p); | ||||
| extern int32_t gpio_test_app(void* p); | ||||
| extern int32_t delay_test_app(void* p); | ||||
| extern int32_t gpio_app(void* p); | ||||
| extern int32_t ibutton_app(void* p); | ||||
| extern int32_t irda_app(void* p); | ||||
| extern int32_t irda_monitor_app(void* p); | ||||
| @ -33,6 +34,9 @@ extern int32_t storage_test_app(void* p); | ||||
| extern int32_t subghz_app(void* p); | ||||
| extern int32_t vibro_test_app(void* p); | ||||
| extern int32_t bt_debug_app(void* p); | ||||
| extern int32_t usb_test_app(void* p); | ||||
| extern int32_t usb_mouse_app(void* p); | ||||
| extern int32_t bad_usb_app(void* p); | ||||
| 
 | ||||
| // Plugins
 | ||||
| extern int32_t music_player_app(void* p); | ||||
| @ -47,6 +51,7 @@ extern void nfc_cli_init(); | ||||
| extern void storage_cli_init(); | ||||
| extern void subghz_cli_init(); | ||||
| extern void power_cli_init(); | ||||
| extern void unit_tests_cli_init(); | ||||
| 
 | ||||
| // Settings
 | ||||
| extern int32_t notification_settings_app(void* p); | ||||
| @ -58,6 +63,10 @@ extern int32_t power_settings_app(void* p); | ||||
| 
 | ||||
| const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| /* Services */ | ||||
| #ifdef SRV_RPC | ||||
|     {.app = rpc_srv, .name = "RPC", .stack_size = 1024 * 4, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_srv, .name = "BT", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| @ -116,24 +125,24 @@ const FlipperApplication FLIPPER_APPS[] = { | ||||
|     {.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IRDA | ||||
|     {.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||
| #ifdef APP_GPIO | ||||
|     {.app = gpio_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_GPIO_TEST | ||||
|     {.app = gpio_test_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14}, | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||
| #endif | ||||
| 
 | ||||
| }; | ||||
| @ -177,6 +186,10 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | ||||
| #ifdef SRV_STORAGE | ||||
|     storage_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UNIT_TESTS | ||||
|     unit_tests_cli_init, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_ON_SYSTEM_START_COUNT = | ||||
| @ -210,8 +223,16 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | ||||
|     {.app = accessor_app, .name = "Accessor", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UNIT_TESTS | ||||
|     {.app = flipper_test_app, .name = "Unit Tests", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #ifdef APP_USB_TEST | ||||
|     {.app = usb_test_app, .name = "USB Test", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_USB_MOUSE | ||||
|     {.app = usb_mouse_app, .name = "USB Mouse demo", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_BAD_USB | ||||
|     {.app = bad_usb_app, .name = "Bad USB test", .stack_size = 2048, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IRDA_MONITOR | ||||
| @ -229,6 +250,10 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_debug_app, .name = "Bluetooth Debug", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_UNIT_TESTS | ||||
|     {.app = delay_test_app, .name = "Delay Test App", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication); | ||||
|  | ||||
| @ -2,8 +2,8 @@ APP_DIR		= $(PROJECT_ROOT)/applications | ||||
| LIB_DIR		= $(PROJECT_ROOT)/lib | ||||
| 
 | ||||
| CFLAGS		+= -I$(APP_DIR) | ||||
| C_SOURCES	+= $(shell find $(APP_DIR) -name *.c) | ||||
| CPP_SOURCES	+= $(shell find $(APP_DIR) -name *.cpp) | ||||
| C_SOURCES	+= $(shell find $(APP_DIR) -name "*.c") | ||||
| CPP_SOURCES	+= $(shell find $(APP_DIR) -name "*.cpp") | ||||
| 
 | ||||
| 
 | ||||
| APP_RELEASE ?= 1 | ||||
| @ -19,12 +19,13 @@ SRV_LOADER	= 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| SRV_POWER	= 1 | ||||
| SRV_POWER_OBSERVER = 1 | ||||
| SRV_RPC = 1 | ||||
| SRV_STORAGE	= 1 | ||||
| 
 | ||||
| # Apps
 | ||||
| SRV_DESKTOP	= 1 | ||||
| APP_ARCHIVE	= 1 | ||||
| APP_GPIO_TEST = 1 | ||||
| APP_GPIO = 1 | ||||
| APP_IBUTTON	= 1 | ||||
| APP_IRDA	= 1 | ||||
| APP_LF_RFID	= 1 | ||||
| @ -41,8 +42,10 @@ APP_BLINK	= 1 | ||||
| APP_IRDA_MONITOR = 1 | ||||
| APP_KEYPAD_TEST = 1 | ||||
| APP_SD_TEST	= 1 | ||||
| APP_UNIT_TESTS = 0 | ||||
| APP_VIBRO_DEMO = 1 | ||||
| APP_USB_TEST = 1 | ||||
| APP_USB_MOUSE = 1 | ||||
| APP_BAD_USB = 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| @ -121,6 +124,27 @@ SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_USB_TEST ?= 0 | ||||
| ifeq ($(APP_USB_TEST), 1) | ||||
| CFLAGS		+= -DAPP_USB_TEST | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_USB_MOUSE ?= 0 | ||||
| ifeq ($(APP_USB_MOUSE), 1) | ||||
| CFLAGS		+= -DAPP_USB_MOUSE | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_BAD_USB ?= 0 | ||||
| ifeq ($(APP_BAD_USB), 1) | ||||
| CFLAGS		+= -DAPP_BAD_USB | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| endif  | ||||
| 
 | ||||
| APP_KEYPAD_TEST ?= 0 | ||||
| ifeq ($(APP_KEYPAD_TEST), 1) | ||||
| CFLAGS		+= -DAPP_KEYPAD_TEST | ||||
| @ -135,9 +159,9 @@ SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_GPIO_TEST ?= 0 | ||||
| ifeq ($(APP_GPIO_TEST), 1) | ||||
| CFLAGS		+= -DAPP_GPIO_TEST | ||||
| APP_GPIO ?= 0 | ||||
| ifeq ($(APP_GPIO), 1) | ||||
| CFLAGS		+= -DAPP_GPIO | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| @ -197,6 +221,10 @@ SRV_GUI		= 1 | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_RPC ?= 0 | ||||
| ifeq ($(SRV_RPC), 1) | ||||
| CFLAGS		+= -DSRV_RPC | ||||
| endif | ||||
| 
 | ||||
| SRV_LOADER ?= 0 | ||||
| ifeq ($(SRV_LOADER), 1) | ||||
|  | ||||
| @ -10,7 +10,9 @@ void archive_update_offset(ArchiveBrowserView* browser) { | ||||
| 
 | ||||
|             if(array_size > 3 && model->idx >= array_size - 1) { | ||||
|                 model->list_offset = model->idx - 3; | ||||
|             } else if(model->last_offset && model->last_offset != model->list_offset) { | ||||
|             } else if( | ||||
|                 model->last_offset && model->last_offset != model->list_offset && | ||||
|                 model->tab_idx == model->last_tab) { | ||||
|                 model->list_offset = model->last_offset; | ||||
|                 model->last_offset = !model->last_offset; | ||||
|             } else if(model->list_offset < model->idx - bounds) { | ||||
| @ -18,7 +20,6 @@ void archive_update_offset(ArchiveBrowserView* browser) { | ||||
|             } else if(model->list_offset > model->idx - bounds) { | ||||
|                 model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -75,6 +76,31 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) { | ||||
|     archive_update_offset(browser); | ||||
| } | ||||
| 
 | ||||
| void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d) { | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             ArchiveFile_t temp; | ||||
|             size_t array_size = files_array_size(model->files) - 1; | ||||
|             uint8_t swap_idx = CLAMP(model->idx + d, array_size, 0); | ||||
| 
 | ||||
|             if(model->idx == 0 && d < 0) { | ||||
|                 ArchiveFile_t_init(&temp); | ||||
|                 files_array_pop_at(&temp, model->files, array_size); | ||||
|                 files_array_push_at(model->files, model->idx, temp); | ||||
|                 ArchiveFile_t_clear(&temp); | ||||
|             } else if(model->idx == array_size && d > 0) { | ||||
|                 ArchiveFile_t_init(&temp); | ||||
|                 files_array_pop_at(&temp, model->files, model->last_idx); | ||||
|                 files_array_push_at(model->files, array_size, temp); | ||||
|                 ArchiveFile_t_clear(&temp); | ||||
|             } else { | ||||
|                 files_array_swap_at(model->files, model->idx, swap_idx); | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void archive_file_array_rm_all(ArchiveBrowserView* browser) { | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
| @ -94,6 +120,18 @@ ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { | ||||
|     return selected; | ||||
| } | ||||
| 
 | ||||
| ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) { | ||||
|     ArchiveFile_t* selected; | ||||
|     idx = CLAMP(idx, archive_file_array_size(browser), 0); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             selected = files_array_size(model->files) ? files_array_get(model->files, idx) : NULL; | ||||
|             return false; | ||||
|         }); | ||||
|     return selected; | ||||
| } | ||||
| 
 | ||||
| ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { | ||||
|     ArchiveTabEnum tab_id; | ||||
|     with_view_model( | ||||
| @ -178,6 +216,14 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) { | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             model->move_fav = active; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void archive_switch_dir(ArchiveBrowserView* browser, const char* path) { | ||||
|     furi_assert(browser); | ||||
|     furi_assert(path); | ||||
|  | ||||
| @ -52,9 +52,11 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target); | ||||
| 
 | ||||
| size_t archive_file_array_size(ArchiveBrowserView* browser); | ||||
| void archive_file_array_rm_selected(ArchiveBrowserView* browser); | ||||
| void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d); | ||||
| void archive_file_array_rm_all(ArchiveBrowserView* browser); | ||||
| 
 | ||||
| ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser); | ||||
| ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx); | ||||
| ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser); | ||||
| uint8_t archive_get_depth(ArchiveBrowserView* browser); | ||||
| const char* archive_get_path(ArchiveBrowserView* browser); | ||||
| @ -62,6 +64,7 @@ const char* archive_get_name(ArchiveBrowserView* browser); | ||||
| 
 | ||||
| void archive_add_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name); | ||||
| void archive_show_file_menu(ArchiveBrowserView* browser, bool show); | ||||
| void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active); | ||||
| 
 | ||||
| void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); | ||||
| void archive_enter_dir(ArchiveBrowserView* browser, string_t name); | ||||
|  | ||||
| @ -34,7 +34,7 @@ uint16_t archive_favorites_count(void* context) { | ||||
| bool archive_favorites_read(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveBrowserView* archive_view = context; | ||||
|     ArchiveBrowserView* browser = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t buffer; | ||||
| @ -52,7 +52,7 @@ bool archive_favorites_read(void* context) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             archive_add_item(archive_view, &file_info, string_get_cstr(buffer)); | ||||
|             archive_add_item(browser, &file_info, string_get_cstr(buffer)); | ||||
|             string_clean(buffer); | ||||
|         } | ||||
|     } | ||||
| @ -63,17 +63,15 @@ bool archive_favorites_read(void* context) { | ||||
| } | ||||
| 
 | ||||
| bool archive_favorites_delete(const char* format, ...) { | ||||
|     string_t buffer; | ||||
|     string_t filename; | ||||
|     va_list args; | ||||
|     va_start(args, format); | ||||
|     uint8_t len = vsnprintf(NULL, 0, format, args); | ||||
|     char filename[len + 1]; | ||||
|     vsnprintf(filename, len + 1, format, args); | ||||
|     string_init_vprintf(filename, format, args); | ||||
|     va_end(args); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     if(result) { | ||||
| @ -85,13 +83,14 @@ bool archive_favorites_delete(const char* format, ...) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(string_search_str(buffer, filename)) { | ||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\r\n", string_get_cstr(buffer)); | ||||
|             if(string_search(buffer, filename)) { | ||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(filename); | ||||
| 
 | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||
| @ -103,16 +102,15 @@ bool archive_favorites_delete(const char* format, ...) { | ||||
| } | ||||
| 
 | ||||
| bool archive_is_favorite(const char* format, ...) { | ||||
|     string_t buffer; | ||||
|     string_t filename; | ||||
|     va_list args; | ||||
|     va_start(args, format); | ||||
|     uint8_t len = vsnprintf(NULL, 0, format, args); | ||||
|     char filename[len + 1]; | ||||
|     vsnprintf(filename, len + 1, format, args); | ||||
|     string_init_vprintf(filename, format, args); | ||||
|     va_end(args); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     bool found = false; | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| @ -125,7 +123,7 @@ bool archive_is_favorite(const char* format, ...) { | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_search_str(buffer, filename)) { | ||||
|             if(!string_search(buffer, filename)) { | ||||
|                 found = true; | ||||
|                 break; | ||||
|             } | ||||
| @ -133,6 +131,7 @@ bool archive_is_favorite(const char* format, ...) { | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(filename); | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_free(file_worker); | ||||
| 
 | ||||
| @ -166,7 +165,7 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | ||||
| 
 | ||||
|             archive_file_append( | ||||
|                 ARCHIVE_FAV_TEMP_PATH, | ||||
|                 "%s\r\n", | ||||
|                 "%s\n", | ||||
|                 string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||
|         } | ||||
|     } | ||||
| @ -186,5 +185,22 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | ||||
| void archive_add_to_favorites(const char* file_path) { | ||||
|     furi_assert(file_path); | ||||
| 
 | ||||
|     archive_file_append(ARCHIVE_FAV_PATH, "%s\r\n", file_path); | ||||
|     archive_file_append(ARCHIVE_FAV_PATH, "%s\n", file_path); | ||||
| } | ||||
| 
 | ||||
| void archive_favorites_save(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveBrowserView* browser = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     for(size_t i = 0; i < archive_file_array_size(browser); i++) { | ||||
|         ArchiveFile_t* item = archive_get_file_at(browser, i); | ||||
|         archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(item->name)); | ||||
|     } | ||||
| 
 | ||||
|     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||
|     file_worker_rename(file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
| 
 | ||||
|     file_worker_free(file_worker); | ||||
| } | ||||
| @ -10,3 +10,4 @@ bool archive_favorites_delete(const char* format, ...); | ||||
| bool archive_is_favorite(const char* format, ...); | ||||
| bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); | ||||
| void archive_add_to_favorites(const char* file_path); | ||||
| void archive_favorites_save(void* context); | ||||
|  | ||||
| @ -138,11 +138,10 @@ bool archive_read_dir(void* context, const char* path) { | ||||
| void archive_file_append(const char* path, const char* format, ...) { | ||||
|     furi_assert(path); | ||||
| 
 | ||||
|     string_t string; | ||||
|     va_list args; | ||||
|     va_start(args, format); | ||||
|     uint8_t len = vsnprintf(NULL, 0, format, args); | ||||
|     char cstr_buff[len + 1]; | ||||
|     vsnprintf(cstr_buff, len + 1, format, args); | ||||
|     string_init_vprintf(string, format, args); | ||||
|     va_end(args); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(false); | ||||
| @ -151,7 +150,7 @@ void archive_file_append(const char* path, const char* format, ...) { | ||||
|         FURI_LOG_E("Archive", "Append open error"); | ||||
|     } | ||||
| 
 | ||||
|     if(!file_worker_write(file_worker, cstr_buff, strlen(cstr_buff))) { | ||||
|     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { | ||||
|         FURI_LOG_E("Archive", "Append write error"); | ||||
|     } | ||||
| 
 | ||||
| @ -159,26 +158,28 @@ void archive_file_append(const char* path, const char* format, ...) { | ||||
|     file_worker_free(file_worker); | ||||
| } | ||||
| 
 | ||||
| void archive_delete_file(void* context, string_t path, string_t name) { | ||||
| void archive_delete_file(void* context, const char* format, ...) { | ||||
|     furi_assert(context); | ||||
|     furi_assert(path); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     string_t filename; | ||||
|     va_list args; | ||||
|     va_start(args, format); | ||||
|     string_init_vprintf(filename, format, args); | ||||
|     va_end(args); | ||||
| 
 | ||||
|     ArchiveBrowserView* browser = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t full_path; | ||||
|     string_init_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); | ||||
| 
 | ||||
|     bool res = file_worker_remove(file_worker, string_get_cstr(full_path)); | ||||
|     bool res = file_worker_remove(file_worker, string_get_cstr(filename)); | ||||
|     file_worker_free(file_worker); | ||||
| 
 | ||||
|     if(archive_is_favorite(string_get_cstr(full_path))) { | ||||
|         archive_favorites_delete(string_get_cstr(full_path)); | ||||
|     if(archive_is_favorite("%s", string_get_cstr(filename))) { | ||||
|         archive_favorites_delete("%s", string_get_cstr(filename)); | ||||
|     } | ||||
| 
 | ||||
|     if(res) { | ||||
|         archive_file_array_rm_selected(browser); | ||||
|     } | ||||
| 
 | ||||
|     string_clear(full_path); | ||||
|     string_clear(filename); | ||||
| } | ||||
| @ -54,4 +54,4 @@ bool archive_get_filenames(void* context, const char* path); | ||||
| bool archive_dir_empty(void* context, const char* path); | ||||
| bool archive_read_dir(void* context, const char* path); | ||||
| void archive_file_append(const char* path, const char* format, ...); | ||||
| void archive_delete_file(void* context, string_t path, string_t name); | ||||
| void archive_delete_file(void* context, const char* format, ...); | ||||
| @ -76,25 +76,32 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||
|             if(favorites) { | ||||
|                 archive_favorites_delete(name); | ||||
|                 archive_file_array_rm_selected(browser); | ||||
|                 archive_show_file_menu(browser, false); | ||||
|             } else if(known_app) { | ||||
|                 if(archive_is_favorite("%s/%s", path, name)) { | ||||
|                     archive_favorites_delete("%s/%s", path, name); | ||||
|                 } else { | ||||
|                     archive_file_append(ARCHIVE_FAV_PATH, "%s/%s\r\n", path, name); | ||||
|                 } | ||||
|                     archive_file_append(ARCHIVE_FAV_PATH, "%s/%s\n", path, name); | ||||
|                 } | ||||
|                 archive_show_file_menu(browser, false); | ||||
|             } | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case ArchiveBrowserEventFileMenuRename: | ||||
|             if(known_app && !favorites) { | ||||
|         case ArchiveBrowserEventFileMenuAction: | ||||
|             if(favorites) { | ||||
|                 browser->callback(ArchiveBrowserEventEnterFavMove, browser->context); | ||||
|             } else if(known_app) { | ||||
|                 scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); | ||||
|             } | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventFileMenuDelete: | ||||
|             archive_delete_file(browser, browser->path, selected->name); | ||||
|             if(favorites) { | ||||
|                 archive_delete_file(browser, "%s", name); | ||||
|             } else { | ||||
|                 archive_delete_file(browser, "%s/%s", path, name); | ||||
|             } | ||||
|             archive_show_file_menu(browser, false); | ||||
|             consumed = true; | ||||
|             break; | ||||
| @ -102,6 +109,30 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||
|             archive_enter_dir(browser, selected->name); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventFavMoveUp: | ||||
|             archive_file_array_swap(browser, 1); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventFavMoveDown: | ||||
|             archive_file_array_swap(browser, -1); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventEnterFavMove: | ||||
|             strlcpy(archive->text_store, archive_get_name(browser), MAX_NAME_LEN); | ||||
|             archive_show_file_menu(browser, false); | ||||
|             archive_favorites_move_mode(archive->browser, true); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventExitFavMove: | ||||
|             archive_update_focus(browser, archive->text_store); | ||||
|             archive_favorites_move_mode(archive->browser, false); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventSaveFavMove: | ||||
|             archive_favorites_move_mode(archive->browser, false); | ||||
|             archive_favorites_save(archive->browser); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case ArchiveBrowserEventExit: | ||||
|             if(archive_get_depth(browser)) { | ||||
|  | ||||
| @ -55,7 +55,7 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { | ||||
|         string_set_str(menu[1], "Unpin"); | ||||
|     } else if(model->tab_idx == ArchiveTabFavorites) { | ||||
|         string_set_str(menu[1], "Unpin"); | ||||
|         string_set_str(menu[2], "---"); | ||||
|         string_set_str(menu[2], "Move"); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < MENU_ITEMS; i++) { | ||||
| @ -66,16 +66,23 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { | ||||
|     canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); | ||||
| } | ||||
| 
 | ||||
| static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||
| static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar, bool moving) { | ||||
|     uint8_t x_offset = moving ? MOVE_OFFSET : 0; | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); | ||||
|     canvas_draw_box( | ||||
|         canvas, | ||||
|         0 + x_offset, | ||||
|         15 + idx * FRAME_HEIGHT, | ||||
|         (scrollbar ? 122 : 127) - x_offset, | ||||
|         FRAME_HEIGHT); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); | ||||
|     canvas_draw_dot(canvas, 0 + x_offset, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 1 + x_offset, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 0 + x_offset, (15 + idx * FRAME_HEIGHT) + 1); | ||||
| 
 | ||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); | ||||
|     canvas_draw_dot(canvas, 0 + x_offset, (15 + idx * FRAME_HEIGHT) + 11); | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); | ||||
| } | ||||
| @ -89,23 +96,26 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { | ||||
|     for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { | ||||
|         string_t str_buff; | ||||
|         char cstr_buff[MAX_NAME_LEN]; | ||||
| 
 | ||||
|         size_t idx = CLAMP(i + model->list_offset, array_size, 0); | ||||
|         uint8_t x_offset = (model->move_fav && model->idx == idx) ? MOVE_OFFSET : 0; | ||||
| 
 | ||||
|         ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); | ||||
| 
 | ||||
|         strlcpy(cstr_buff, string_get_cstr(file->name), string_size(file->name) + 1); | ||||
|         archive_trim_file_path(cstr_buff, is_known_app(file->type)); | ||||
|         string_init_set_str(str_buff, cstr_buff); | ||||
|         elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); | ||||
|         elements_string_fit_width( | ||||
|             canvas, str_buff, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset); | ||||
| 
 | ||||
|         if(model->idx == idx) { | ||||
|             archive_draw_frame(canvas, i, scrollbar); | ||||
|             archive_draw_frame(canvas, i, scrollbar, model->move_fav); | ||||
|         } else { | ||||
|             canvas_set_color(canvas, ColorBlack); | ||||
|         } | ||||
| 
 | ||||
|         canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); | ||||
|         canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||
|         canvas_draw_icon( | ||||
|             canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); | ||||
|         canvas_draw_str(canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||
|         string_clear(str_buff); | ||||
|     } | ||||
| 
 | ||||
| @ -139,8 +149,13 @@ static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* m | ||||
|     canvas_draw_line(canvas, 107, 1, 107, 11); | ||||
|     canvas_draw_line(canvas, 108, 12, 126, 12); | ||||
| 
 | ||||
|     if(model->move_fav) { | ||||
|         canvas_draw_icon(canvas, 111, 4, &I_ButtonUp_7x4); | ||||
|         canvas_draw_icon(canvas, 118, 4, &I_ButtonDown_7x4); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); | ||||
|         canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); | ||||
|     } | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_dot(canvas, 50, 0); | ||||
| @ -174,9 +189,11 @@ bool archive_view_input(InputEvent* event, void* context) { | ||||
|     ArchiveBrowserView* browser = context; | ||||
| 
 | ||||
|     bool in_menu; | ||||
|     bool move_fav_mode; | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             in_menu = model->menu; | ||||
|             move_fav_mode = model->move_fav; | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
| @ -210,11 +227,17 @@ bool archive_view_input(InputEvent* event, void* context) { | ||||
|     } else { | ||||
|         if(event->type == InputTypeShort) { | ||||
|             if(event->key == InputKeyLeft || event->key == InputKeyRight) { | ||||
|                 if(move_fav_mode) return false; | ||||
|                 archive_switch_tab(browser, event->key); | ||||
|             } else if(event->key == InputKeyBack) { | ||||
|                 if(move_fav_mode) { | ||||
|                     browser->callback(ArchiveBrowserEventExitFavMove, browser->context); | ||||
|                 } else { | ||||
|                     browser->callback(ArchiveBrowserEventExit, browser->context); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||
|             with_view_model( | ||||
|                 browser->view, (ArchiveBrowserViewModel * model) { | ||||
| @ -222,8 +245,15 @@ bool archive_view_input(InputEvent* event, void* context) { | ||||
|                     if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|                         if(event->key == InputKeyUp) { | ||||
|                             model->idx = ((model->idx - 1) + num_elements) % num_elements; | ||||
|                             if(move_fav_mode) { | ||||
|                                 browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); | ||||
|                             } | ||||
|                         } else if(event->key == InputKeyDown) { | ||||
|                             model->idx = (model->idx + 1) % num_elements; | ||||
|                             if(move_fav_mode) { | ||||
|                                 browser->callback( | ||||
|                                     ArchiveBrowserEventFavMoveDown, browser->context); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
| @ -241,14 +271,20 @@ bool archive_view_input(InputEvent* event, void* context) { | ||||
| 
 | ||||
|                 if(event->type == InputTypeShort) { | ||||
|                     if(favorites) { | ||||
|                         if(move_fav_mode) { | ||||
|                             browser->callback(ArchiveBrowserEventSaveFavMove, browser->context); | ||||
|                         } else { | ||||
|                             browser->callback(ArchiveBrowserEventFileMenuRun, browser->context); | ||||
|                         } | ||||
|                     } else if(folder) { | ||||
|                         browser->callback(ArchiveBrowserEventEnterDir, browser->context); | ||||
|                     } else { | ||||
|                         browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); | ||||
|                     } | ||||
|                 } else if(event->type == InputTypeLong) { | ||||
|                     if(folder || favorites) { | ||||
|                     if(move_fav_mode) { | ||||
|                         browser->callback(ArchiveBrowserEventSaveFavMove, browser->context); | ||||
|                     } else if(folder || favorites) { | ||||
|                         browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| @ -14,14 +14,15 @@ | ||||
| #define FRAME_HEIGHT 12 | ||||
| #define MENU_ITEMS 4 | ||||
| #define MAX_DEPTH 32 | ||||
| #define MOVE_OFFSET 5 | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveTabFavorites, | ||||
|     ArchiveTabLFRFID, | ||||
|     ArchiveTabSubGhz, | ||||
|     ArchiveTabLFRFID, | ||||
|     ArchiveTabNFC, | ||||
|     ArchiveTabIButton, | ||||
|     ArchiveTabIrda, | ||||
|     ArchiveTabIButton, | ||||
|     ArchiveTabBrowser, | ||||
|     ArchiveTabTotal, | ||||
| } ArchiveTabEnum; | ||||
| @ -31,16 +32,21 @@ typedef enum { | ||||
|     ArchiveBrowserEventFileMenuClose, | ||||
|     ArchiveBrowserEventFileMenuRun, | ||||
|     ArchiveBrowserEventFileMenuPin, | ||||
|     ArchiveBrowserEventFileMenuRename, | ||||
|     ArchiveBrowserEventFileMenuAction, | ||||
|     ArchiveBrowserEventFileMenuDelete, | ||||
|     ArchiveBrowserEventEnterDir, | ||||
|     ArchiveBrowserEventFavMoveUp, | ||||
|     ArchiveBrowserEventFavMoveDown, | ||||
|     ArchiveBrowserEventEnterFavMove, | ||||
|     ArchiveBrowserEventExitFavMove, | ||||
|     ArchiveBrowserEventSaveFavMove, | ||||
|     ArchiveBrowserEventExit, | ||||
| } ArchiveBrowserEvent; | ||||
| 
 | ||||
| static const uint8_t file_menu_actions[MENU_ITEMS] = { | ||||
|     [0] = ArchiveBrowserEventFileMenuRun, | ||||
|     [1] = ArchiveBrowserEventFileMenuPin, | ||||
|     [2] = ArchiveBrowserEventFileMenuRename, | ||||
|     [2] = ArchiveBrowserEventFileMenuAction, | ||||
|     [3] = ArchiveBrowserEventFileMenuDelete, | ||||
| }; | ||||
| 
 | ||||
| @ -68,6 +74,7 @@ typedef struct { | ||||
|     files_array_t files; | ||||
| 
 | ||||
|     uint8_t menu_idx; | ||||
|     bool move_fav; | ||||
|     bool menu; | ||||
| 
 | ||||
|     uint16_t idx; | ||||
|  | ||||
| @ -33,7 +33,10 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) | ||||
|     Bt* bt = context; | ||||
|     const PowerEvent* event = _event; | ||||
|     if(event->type == PowerEventTypeBatteryLevelChanged) { | ||||
|         bt_update_battery_level(bt, event->data.battery_level); | ||||
|         BtMessage message = { | ||||
|             .type = BtMessageTypeUpdateBatteryLevel, | ||||
|             .data.battery_level = event->data.battery_level}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -61,9 +64,84 @@ Bt* bt_alloc() { | ||||
|     PubSub* power_pubsub = power_get_pubsub(bt->power); | ||||
|     subscribe_pubsub(power_pubsub, bt_battery_level_changed_callback, bt); | ||||
| 
 | ||||
|     // RPC
 | ||||
|     bt->rpc = furi_record_open("rpc"); | ||||
|     bt->rpc_sem = osSemaphoreNew(1, 0, NULL); | ||||
| 
 | ||||
|     return bt; | ||||
| } | ||||
| 
 | ||||
| // Called from GAP thread from Serial service
 | ||||
| static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) { | ||||
|     furi_assert(context); | ||||
|     Bt* bt = context; | ||||
| 
 | ||||
|     size_t bytes_processed = rpc_feed_bytes(bt->rpc_session, data, size, 1000); | ||||
|     if(bytes_processed != size) { | ||||
|         FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Called from GAP thread from Serial service
 | ||||
| static void bt_on_data_sent_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Bt* bt = context; | ||||
| 
 | ||||
|     osSemaphoreRelease(bt->rpc_sem); | ||||
| } | ||||
| 
 | ||||
| // Called from RPC thread
 | ||||
| static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { | ||||
|     furi_assert(context); | ||||
|     Bt* bt = context; | ||||
| 
 | ||||
|     size_t bytes_sent = 0; | ||||
|     while(bytes_sent < bytes_len) { | ||||
|         size_t bytes_remain = bytes_len - bytes_sent; | ||||
|         if(bytes_remain > FURI_HAL_BT_PACKET_SIZE_MAX) { | ||||
|             furi_hal_bt_tx(&bytes[bytes_sent], FURI_HAL_BT_PACKET_SIZE_MAX); | ||||
|             bytes_sent += FURI_HAL_BT_PACKET_SIZE_MAX; | ||||
|         } else { | ||||
|             furi_hal_bt_tx(&bytes[bytes_sent], bytes_remain); | ||||
|             bytes_sent += bytes_remain; | ||||
|         } | ||||
|         osSemaphoreAcquire(bt->rpc_sem, osWaitForever); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Called from GAP thread
 | ||||
| static void bt_on_gap_event_callback(BleEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     Bt* bt = context; | ||||
| 
 | ||||
|     if(event.type == BleEventTypeConnected) { | ||||
|         FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection"); | ||||
|         bt->rpc_session = rpc_open_session(bt->rpc); | ||||
|         rpc_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback, bt); | ||||
|         furi_hal_bt_set_data_event_callbacks( | ||||
|             bt_on_data_received_callback, bt_on_data_sent_callback, bt); | ||||
|         // Update battery level
 | ||||
|         PowerInfo info; | ||||
|         power_get_info(bt->power, &info); | ||||
|         BtMessage message = { | ||||
|             .type = BtMessageTypeUpdateBatteryLevel, .data.battery_level = info.charge}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } else if(event.type == BleEventTypeDisconnected) { | ||||
|         FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection"); | ||||
|         if(bt->rpc_session) { | ||||
|             rpc_close_session(bt->rpc_session); | ||||
|             bt->rpc_session = NULL; | ||||
|         } | ||||
|     } else if(event.type == BleEventTypeStartAdvertising || event.type == BleEventTypeStopAdvertising) { | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } else if(event.type == BleEventTypePinCodeShow) { | ||||
|         BtMessage message = { | ||||
|             .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int32_t bt_srv() { | ||||
|     Bt* bt = bt_alloc(); | ||||
|     furi_record_create("bt", bt); | ||||
| @ -72,11 +150,10 @@ int32_t bt_srv() { | ||||
|         FURI_LOG_E(BT_SERVICE_TAG, "Core2 startup failed"); | ||||
|     } else { | ||||
|         view_port_enabled_set(bt->statusbar_view_port, true); | ||||
|         if(furi_hal_bt_init_app()) { | ||||
|         if(furi_hal_bt_init_app(bt_on_gap_event_callback, bt)) { | ||||
|             FURI_LOG_I(BT_SERVICE_TAG, "BLE stack started"); | ||||
|             if(bt->bt_settings.enabled) { | ||||
|                 furi_hal_bt_start_advertising(); | ||||
|                 FURI_LOG_I(BT_SERVICE_TAG, "Start advertising"); | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_E(BT_SERVICE_TAG, "BT App start failed"); | ||||
|  | ||||
| @ -9,12 +9,6 @@ extern "C" { | ||||
| 
 | ||||
| typedef struct Bt Bt; | ||||
| 
 | ||||
| void bt_update_statusbar(Bt* bt); | ||||
| 
 | ||||
| void bt_update_battery_level(Bt* bt, uint8_t battery_level); | ||||
| 
 | ||||
| bool bt_pin_code_show(Bt* bt, uint32_t pin_code); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -1,21 +0,0 @@ | ||||
| #include "bt.h" | ||||
| #include "bt_i.h" | ||||
| 
 | ||||
| void bt_update_statusbar(Bt* bt) { | ||||
|     furi_assert(bt); | ||||
|     BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||
|     furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| void bt_update_battery_level(Bt* bt, uint8_t battery_level) { | ||||
|     furi_assert(bt); | ||||
|     BtMessage message = { | ||||
|         .type = BtMessageTypeUpdateBatteryLevel, .data.battery_level = battery_level}; | ||||
|     furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| bool bt_pin_code_show(Bt* bt, uint32_t pin_code) { | ||||
|     furi_assert(bt); | ||||
|     BtMessage message = {.type = BtMessageTypePinCodeShow, .data.pin_code = pin_code}; | ||||
|     return osMessageQueuePut(bt->message_queue, &message, 0, 0) == osOK; | ||||
| } | ||||
| @ -11,6 +11,7 @@ | ||||
| 
 | ||||
| #include <dialogs/dialogs.h> | ||||
| #include <power/power_service/power.h> | ||||
| #include <applications/rpc/rpc.h> | ||||
| 
 | ||||
| #include "../bt_settings.h" | ||||
| 
 | ||||
| @ -38,4 +39,7 @@ struct Bt { | ||||
|     DialogsApp* dialogs; | ||||
|     DialogMessage* dialog_message; | ||||
|     Power* power; | ||||
|     Rpc* rpc; | ||||
|     RpcSession* rpc_session; | ||||
|     osSemaphoreId_t rpc_sem; | ||||
| }; | ||||
|  | ||||
| @ -1,11 +1,16 @@ | ||||
| /**
 | ||||
|  * @file cli.h | ||||
|  * Cli API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     CliSymbolAsciiSOH = 0x01, | ||||
|     CliSymbolAsciiETX = 0x03, | ||||
| @ -21,30 +26,29 @@ typedef enum { | ||||
| } CliSymbols; | ||||
| 
 | ||||
| typedef enum { | ||||
|     CliCommandFlagDefault = 0, /** Default, loader lock is used */ | ||||
|     CliCommandFlagDefault = 0, /**< Default, loader lock is used */ | ||||
|     CliCommandFlagParallelSafe = | ||||
|         (1 << 0), /** Safe to run in parallel with other apps, loader lock is not used */ | ||||
|     CliCommandFlagInsomniaSafe = (1 << 1), /** Safe to run with insomnia mode on */ | ||||
|         (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ | ||||
|     CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ | ||||
| } CliCommandFlag; | ||||
| 
 | ||||
| /* Cli type
 | ||||
|  * Anonymous structure. Use cli_i.h if you need to go deeper. | ||||
|  */ | ||||
| /** Cli type anonymous structure */ | ||||
| typedef struct Cli Cli; | ||||
| 
 | ||||
| /* Cli callback function pointer.
 | ||||
|  * Implement this interface and use add_cli_command | ||||
|  * @param args - string with what was passed after command | ||||
|  * @param context - pointer to whatever you gave us on cli_add_command | ||||
| /** Cli callback function pointer. Implement this interface and use
 | ||||
|  * add_cli_command | ||||
|  * @param      args     string with what was passed after command | ||||
|  * @param      context  pointer to whatever you gave us on cli_add_command | ||||
|  */ | ||||
| typedef void (*CliCallback)(Cli* cli, string_t args, void* context); | ||||
| 
 | ||||
| /* Add cli command
 | ||||
|  * Registers you command callback | ||||
|  * @param cli - pointer to cli instance | ||||
|  * @param name - command name | ||||
|  * @param callback - callback function | ||||
|  * @param context - pointer to whatever we need to pass to callback | ||||
| /** Add cli command Registers you command callback
 | ||||
|  * | ||||
|  * @param      cli       pointer to cli instance | ||||
|  * @param      name      command name | ||||
|  * @param      flags     CliCommandFlag | ||||
|  * @param      callback  callback function | ||||
|  * @param      context   pointer to whatever we need to pass to callback | ||||
|  */ | ||||
| void cli_add_command( | ||||
|     Cli* cli, | ||||
| @ -53,51 +57,56 @@ void cli_add_command( | ||||
|     CliCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| /* Print unified cmd usage tip
 | ||||
|  * @param cmd - cmd name | ||||
|  * @param usage - usage tip | ||||
|  * @param arg - arg passed by user | ||||
| /** Print unified cmd usage tip
 | ||||
|  * | ||||
|  * @param      cmd    cmd name | ||||
|  * @param      usage  usage tip | ||||
|  * @param      arg    arg passed by user | ||||
|  */ | ||||
| 
 | ||||
| void cli_print_usage(const char* cmd, const char* usage, const char* arg); | ||||
| 
 | ||||
| /* Delete cli command
 | ||||
|  * @param cli - pointer to cli instance | ||||
|  * @param name - command name | ||||
| /** Delete cli command
 | ||||
|  * | ||||
|  * @param      cli   pointer to cli instance | ||||
|  * @param      name  command name | ||||
|  */ | ||||
| void cli_delete_command(Cli* cli, const char* name); | ||||
| 
 | ||||
| /* Read from terminal
 | ||||
|  * Do it only from inside of cli call. | ||||
|  * @param cli - Cli instance | ||||
|  * @param buffer - pointer to buffer | ||||
|  * @param size - size of buffer in bytes | ||||
| /** Read from terminal Do it only from inside of cli call.
 | ||||
|  * | ||||
|  * @param      cli     Cli instance | ||||
|  * @param      buffer  pointer to buffer | ||||
|  * @param      size    size of buffer in bytes | ||||
|  * | ||||
|  * @return     bytes written | ||||
|  */ | ||||
| size_t cli_read(Cli* cli, uint8_t* buffer, size_t size); | ||||
| 
 | ||||
| /* Not blocking check for interrupt command received
 | ||||
|  * @param cli - Cli instance | ||||
| /** Not blocking check for interrupt command received
 | ||||
|  * | ||||
|  * @param      cli   Cli instance | ||||
|  * | ||||
|  * @return     true if received | ||||
|  */ | ||||
| bool cli_cmd_interrupt_received(Cli* cli); | ||||
| 
 | ||||
| /* Write to terminal
 | ||||
|  * Do it only from inside of cli call. | ||||
|  * @param cli - Cli instance | ||||
|  * @param buffer - pointer to buffer | ||||
|  * @param size - size of buffer in bytes | ||||
|  * @return bytes written | ||||
| /** Write to terminal Do it only from inside of cli call.
 | ||||
|  * | ||||
|  * @param      cli     Cli instance | ||||
|  * @param      buffer  pointer to buffer | ||||
|  * @param      size    size of buffer in bytes | ||||
|  */ | ||||
| void cli_write(Cli* cli, const uint8_t* buffer, size_t size); | ||||
| 
 | ||||
| /* Read character
 | ||||
|  * @param cli - Cli instance | ||||
| /** Read character
 | ||||
|  * | ||||
|  * @param      cli   Cli instance | ||||
|  * | ||||
|  * @return     char | ||||
|  */ | ||||
| char cli_getc(Cli* cli); | ||||
| 
 | ||||
| /* New line 
 | ||||
|  * Send new ine sequence | ||||
| /** New line Send new ine sequence
 | ||||
|  */ | ||||
| void cli_nl(); | ||||
| 
 | ||||
|  | ||||
| @ -7,14 +7,47 @@ | ||||
| #include <notification/notification-messages.h> | ||||
| #include <shci.h> | ||||
| 
 | ||||
| #define ENCLAVE_SIGNATURE_KEY_SLOT 1 | ||||
| #define ENCLAVE_SIGNATURE_KEY_SLOTS 10 | ||||
| #define ENCLAVE_SIGNATURE_SIZE 16 | ||||
| static const uint8_t enclave_signature_iv[16] = | ||||
|     {0x32, 0xe6, 0xa7, 0x85, 0x20, 0xae, 0x0b, 0xf0, 0x00, 0xb6, 0x30, 0x9b, 0xd5, 0x42, 0x9e, 0xa6}; | ||||
| static const uint8_t enclave_signature_input[ENCLAVE_SIGNATURE_SIZE] = | ||||
|     {0xdc, 0x76, 0x15, 0x1e, 0x69, 0xe8, 0xdc, 0xd3, 0x4a, 0x71, 0x0b, 0x42, 0x71, 0xe0, 0xa9, 0x78}; | ||||
| static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_SIZE] = | ||||
|     {0x1b, 0xb3, 0xcf, 0x16, 0xc, 0x27, 0xf7, 0xf2, 0xf0, 0x7e, 0x5f, 0xbe, 0xfe, 0x89, 0x52, 0xe1}; | ||||
| 
 | ||||
| static const uint8_t enclave_signature_iv[ENCLAVE_SIGNATURE_KEY_SLOTS][16] = { | ||||
|     {0xac, 0x5d, 0x68, 0xb8, 0x79, 0x74, 0xfc, 0x7f, 0x45, 0x02, 0x82, 0xf1, 0x48, 0x7e, 0x75, 0x8a}, | ||||
|     {0x38, 0xe6, 0x6a, 0x90, 0x5e, 0x5b, 0x8a, 0xa6, 0x70, 0x30, 0x04, 0x72, 0xc2, 0x42, 0xea, 0xaf}, | ||||
|     {0x73, 0xd5, 0x8e, 0xfb, 0x0f, 0x4b, 0xa9, 0x79, 0x0f, 0xde, 0x0e, 0x53, 0x44, 0x7d, 0xaa, 0xfd}, | ||||
|     {0x3c, 0x9a, 0xf4, 0x43, 0x2b, 0xfe, 0xea, 0xae, 0x8c, 0xc6, 0xd1, 0x60, 0xd2, 0x96, 0x64, 0xa9}, | ||||
|     {0x10, 0xac, 0x7b, 0x63, 0x03, 0x7f, 0x43, 0x18, 0xec, 0x9d, 0x9c, 0xc4, 0x01, 0xdc, 0x35, 0xa7}, | ||||
|     {0x26, 0x21, 0x64, 0xe6, 0xd0, 0xf2, 0x47, 0x49, 0xdc, 0x36, 0xcd, 0x68, 0x0c, 0x91, 0x03, 0x44}, | ||||
|     {0x7a, 0xbd, 0xce, 0x9c, 0x24, 0x7a, 0x2a, 0xb1, 0x3c, 0x4f, 0x5a, 0x7d, 0x80, 0x3e, 0xfc, 0x0d}, | ||||
|     {0xcd, 0xdd, 0xd3, 0x02, 0x85, 0x65, 0x43, 0x83, 0xf9, 0xac, 0x75, 0x2f, 0x21, 0xef, 0x28, 0x6b}, | ||||
|     {0xab, 0x73, 0x70, 0xe8, 0xe2, 0x56, 0x0f, 0x58, 0xab, 0x29, 0xa5, 0xb1, 0x13, 0x47, 0x5e, 0xe8}, | ||||
|     {0x4f, 0x3c, 0x43, 0x77, 0xde, 0xed, 0x79, 0xa1, 0x8d, 0x4c, 0x1f, 0xfd, 0xdb, 0x96, 0x87, 0x2e}, | ||||
| }; | ||||
| 
 | ||||
| static const uint8_t enclave_signature_input[ENCLAVE_SIGNATURE_KEY_SLOTS][ENCLAVE_SIGNATURE_SIZE] = { | ||||
|     {0x9f, 0x5c, 0xb1, 0x43, 0x17, 0x53, 0x18, 0x8c, 0x66, 0x3d, 0x39, 0x45, 0x90, 0x13, 0xa9, 0xde}, | ||||
|     {0xc5, 0x98, 0xe9, 0x17, 0xb8, 0x97, 0x9e, 0x03, 0x33, 0x14, 0x13, 0x8f, 0xce, 0x74, 0x0d, 0x54}, | ||||
|     {0x34, 0xba, 0x99, 0x59, 0x9f, 0x70, 0x67, 0xe9, 0x09, 0xee, 0x64, 0x0e, 0xb3, 0xba, 0xfb, 0x75}, | ||||
|     {0xdc, 0xfa, 0x6c, 0x9a, 0x6f, 0x0a, 0x3e, 0xdc, 0x42, 0xf6, 0xae, 0x0d, 0x3c, 0xf7, 0x83, 0xaf}, | ||||
|     {0xea, 0x2d, 0xe3, 0x1f, 0x02, 0x99, 0x1a, 0x7e, 0x6d, 0x93, 0x4c, 0xb5, 0x42, 0xf0, 0x7a, 0x9b}, | ||||
|     {0x53, 0x5e, 0x04, 0xa2, 0x49, 0xa0, 0x73, 0x49, 0x56, 0xb0, 0x88, 0x8c, 0x12, 0xa0, 0xe4, 0x18}, | ||||
|     {0x7d, 0xa7, 0xc5, 0x21, 0x7f, 0x12, 0x95, 0xdd, 0x4d, 0x77, 0x01, 0xfa, 0x71, 0x88, 0x2b, 0x7f}, | ||||
|     {0xdc, 0x9b, 0xc5, 0xa7, 0x6b, 0x84, 0x5c, 0x37, 0x7c, 0xec, 0x05, 0xa1, 0x9f, 0x91, 0x17, 0x3b}, | ||||
|     {0xea, 0xcf, 0xd9, 0x9b, 0x86, 0xcd, 0x2b, 0x43, 0x54, 0x45, 0x82, 0xc6, 0xfe, 0x73, 0x1a, 0x1a}, | ||||
|     {0x77, 0xb8, 0x1b, 0x90, 0xb4, 0xb7, 0x32, 0x76, 0x8f, 0x8a, 0x57, 0x06, 0xc7, 0xdd, 0x08, 0x90}, | ||||
| }; | ||||
| 
 | ||||
| static const uint8_t enclave_signature_expected[ENCLAVE_SIGNATURE_KEY_SLOTS][ENCLAVE_SIGNATURE_SIZE] = { | ||||
|     {0xe9, 0x9a, 0xce, 0xe9, 0x4d, 0xe1, 0x7f, 0x55, 0xcb, 0x8a, 0xbf, 0xf2, 0x4d, 0x98, 0x27, 0x67}, | ||||
|     {0x34, 0x27, 0xa7, 0xea, 0xa8, 0x98, 0x66, 0x9b, 0xed, 0x43, 0xd3, 0x93, 0xb5, 0xa2, 0x87, 0x8e}, | ||||
|     {0x6c, 0xf3, 0x01, 0x78, 0x53, 0x1b, 0x11, 0x32, 0xf0, 0x27, 0x2f, 0xe3, 0x7d, 0xa6, 0xe2, 0xfd}, | ||||
|     {0xdf, 0x7f, 0x37, 0x65, 0x2f, 0xdb, 0x7c, 0xcf, 0x5b, 0xb6, 0xe4, 0x9c, 0x63, 0xc5, 0x0f, 0xe0}, | ||||
|     {0x9b, 0x5c, 0xee, 0x44, 0x0e, 0xd1, 0xcb, 0x5f, 0x28, 0x9f, 0x12, 0x17, 0x59, 0x64, 0x40, 0xbb}, | ||||
|     {0x94, 0xc2, 0x09, 0x98, 0x62, 0xa7, 0x2b, 0x93, 0xed, 0x36, 0x1f, 0x10, 0xbc, 0x26, 0xbd, 0x41}, | ||||
|     {0x4d, 0xb2, 0x2b, 0xc5, 0x96, 0x47, 0x61, 0xf4, 0x16, 0xe0, 0x81, 0xc3, 0x8e, 0xb9, 0x9c, 0x9b}, | ||||
|     {0xc3, 0x6b, 0x83, 0x55, 0x90, 0x38, 0x0f, 0xea, 0xd1, 0x65, 0xbf, 0x32, 0x4f, 0x8e, 0x62, 0x5b}, | ||||
|     {0x8d, 0x5e, 0x27, 0xbc, 0x14, 0x4f, 0x08, 0xa8, 0x2b, 0x14, 0x89, 0x5e, 0xdf, 0x77, 0x04, 0x31}, | ||||
|     {0xc9, 0xf7, 0x03, 0xf1, 0x6c, 0x65, 0xad, 0x49, 0x74, 0xbe, 0x00, 0x54, 0xfd, 0xa6, 0x9c, 0x32}, | ||||
| }; | ||||
| 
 | ||||
| /* 
 | ||||
|  * Device Info Command | ||||
| @ -99,15 +132,23 @@ void cli_command_device_info(Cli* cli, string_t args, void* context) { | ||||
| 
 | ||||
|         // Signature verification
 | ||||
|         uint8_t buffer[ENCLAVE_SIGNATURE_SIZE]; | ||||
|         bool enclave_valid = false; | ||||
|         if(furi_hal_crypto_store_load_key(ENCLAVE_SIGNATURE_KEY_SLOT, enclave_signature_iv)) { | ||||
|             if(furi_hal_crypto_encrypt(enclave_signature_input, buffer, ENCLAVE_SIGNATURE_SIZE)) { | ||||
|                 enclave_valid = | ||||
|                     memcmp(buffer, enclave_signature_expected, ENCLAVE_SIGNATURE_SIZE) == 0; | ||||
|         size_t enclave_valid_keys = 0; | ||||
|         for(size_t key_slot = 0; key_slot < ENCLAVE_SIGNATURE_KEY_SLOTS; key_slot++) { | ||||
|             if(furi_hal_crypto_store_load_key(key_slot + 1, enclave_signature_iv[key_slot])) { | ||||
|                 if(furi_hal_crypto_encrypt( | ||||
|                        enclave_signature_input[key_slot], buffer, ENCLAVE_SIGNATURE_SIZE)) { | ||||
|                     enclave_valid_keys += memcmp( | ||||
|                                               buffer, | ||||
|                                               enclave_signature_expected[key_slot], | ||||
|                                               ENCLAVE_SIGNATURE_SIZE) == 0; | ||||
|                 } | ||||
|             furi_hal_crypto_store_unload_key(ENCLAVE_SIGNATURE_KEY_SLOT); | ||||
|                 furi_hal_crypto_store_unload_key(key_slot + 1); | ||||
|             } | ||||
|         printf("enclave_valid       : %s\r\n", enclave_valid ? "true" : "false"); | ||||
|         } | ||||
|         printf("enclave_valid_keys  : %d\r\n", enclave_valid_keys); | ||||
|         printf( | ||||
|             "enclave_valid       : %s\r\n", | ||||
|             (enclave_valid_keys == ENCLAVE_SIGNATURE_KEY_SLOTS) ? "true" : "false"); | ||||
|     } else { | ||||
|         printf("radio_alive         : false\r\n"); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										364
									
								
								applications/debug_tools/bad_usb.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								applications/debug_tools/bad_usb.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,364 @@ | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include <gui/gui.h> | ||||
| #include <input/input.h> | ||||
| #include <lib/toolbox/args.h> | ||||
| #include <furi-hal-usb-hid.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     EventTypeInput, | ||||
|     EventTypeWorkerState, | ||||
| } EventType; | ||||
| 
 | ||||
| typedef enum { | ||||
|     WorkerStateDone, | ||||
|     WorkerStateNoFile, | ||||
|     WorkerStateScriptError, | ||||
|     WorkerStateDisconnected, | ||||
| } WorkerState; | ||||
| 
 | ||||
| typedef enum { | ||||
|     AppStateWait, | ||||
|     AppStateRunning, | ||||
|     AppStateError, | ||||
|     AppStateExit, | ||||
| } AppState; | ||||
| 
 | ||||
| typedef enum { | ||||
|     WorkerCmdStart = (1 << 0), | ||||
|     WorkerCmdStop = (1 << 1), | ||||
| } WorkerCommandFlags; | ||||
| 
 | ||||
| // Event message from worker
 | ||||
| typedef struct { | ||||
|     WorkerState state; | ||||
|     uint16_t line; | ||||
| } BadUsbWorkerState; | ||||
| 
 | ||||
| typedef struct { | ||||
|     union { | ||||
|         InputEvent input; | ||||
|         BadUsbWorkerState worker; | ||||
|     }; | ||||
|     EventType type; | ||||
| } BadUsbEvent; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t defdelay; | ||||
|     char msg_text[32]; | ||||
|     osThreadAttr_t thread_attr; | ||||
|     osThreadId_t thread; | ||||
|     osMessageQueueId_t event_queue; | ||||
| } BadUsbParams; | ||||
| 
 | ||||
| typedef struct { | ||||
|     char* name; | ||||
|     uint16_t keycode; | ||||
| } DuckyKey; | ||||
| 
 | ||||
| static const DuckyKey ducky_keys[] = { | ||||
|     {"CTRL", KEY_MOD_LEFT_CTRL}, | ||||
|     {"CONTROL", KEY_MOD_LEFT_CTRL}, | ||||
|     {"SHIFT", KEY_MOD_LEFT_SHIFT}, | ||||
|     {"ALT", KEY_MOD_LEFT_ALT}, | ||||
|     {"GUI", KEY_MOD_LEFT_GUI}, | ||||
|     {"WINDOWS", KEY_MOD_LEFT_GUI}, | ||||
| 
 | ||||
|     {"DOWNARROW", KEY_DOWN_ARROW}, | ||||
|     {"DOWN", KEY_DOWN_ARROW}, | ||||
|     {"LEFTARROW", KEY_LEFT_ARROW}, | ||||
|     {"LEFT", KEY_LEFT_ARROW}, | ||||
|     {"RIGHTARROW", KEY_RIGHT_ARROW}, | ||||
|     {"RIGHT", KEY_RIGHT_ARROW}, | ||||
|     {"UPARROW", KEY_UP_ARROW}, | ||||
|     {"UP", KEY_UP_ARROW}, | ||||
| 
 | ||||
|     {"ENTER", KEY_ENTER}, | ||||
|     {"BREAK", KEY_PAUSE}, | ||||
|     {"PAUSE", KEY_PAUSE}, | ||||
|     {"CAPSLOCK", KEY_CAPS_LOCK}, | ||||
|     {"DELETE", KEY_DELETE}, | ||||
|     {"BACKSPACE", KEY_BACKSPACE}, | ||||
|     {"END", KEY_END}, | ||||
|     {"ESC", KEY_ESC}, | ||||
|     {"ESCAPE", KEY_ESC}, | ||||
|     {"HOME", KEY_HOME}, | ||||
|     {"INSERT", KEY_INSERT}, | ||||
|     {"NUMLOCK", KEY_NUM_LOCK}, | ||||
|     {"PAGEUP", KEY_PAGE_UP}, | ||||
|     {"PAGEDOWN", KEY_PAGE_DOWN}, | ||||
|     {"PRINTSCREEN", KEY_PRINT}, | ||||
|     {"SCROLLOCK", KEY_SCROLL_LOCK}, | ||||
|     {"SPACE", KEY_SPACE}, | ||||
|     {"TAB", KEY_TAB}, | ||||
|     {"MENU", KEY_APPLICATION}, | ||||
|     {"APP", KEY_APPLICATION}, | ||||
| }; | ||||
| 
 | ||||
| static const char ducky_cmd_comment[] = {"REM"}; | ||||
| static const char ducky_cmd_delay[] = {"DELAY"}; | ||||
| static const char ducky_cmd_string[] = {"STRING"}; | ||||
| static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY"}; | ||||
| static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY"}; | ||||
| 
 | ||||
| static bool ducky_get_delay_val(char* param, uint32_t* val) { | ||||
|     uint32_t delay_val = 0; | ||||
|     if(sscanf(param, "%lu", &delay_val) == 1) { | ||||
|         *val = delay_val; | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| static bool ducky_string(char* param) { | ||||
|     uint32_t i = 0; | ||||
|     while(param[i] != '\0') { | ||||
|         furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i])); | ||||
|         furi_hal_hid_kb_release(HID_ASCII_TO_KEY(param[i])); | ||||
|         i++; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static uint16_t ducky_get_keycode(char* param, bool accept_chars) { | ||||
|     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) | ||||
|             return ducky_keys[i].keycode; | ||||
|     } | ||||
|     if((accept_chars) && (strlen(param) > 0)) { | ||||
|         return (HID_ASCII_TO_KEY(param[0]) & 0xFF); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static bool ducky_parse_line(string_t line, BadUsbParams* app) { | ||||
|     //uint32_t line_len = string_size(line);
 | ||||
|     char* line_t = (char*)string_get_cstr(line); | ||||
|     bool state = false; | ||||
| 
 | ||||
|     // General commands
 | ||||
|     if(strncmp(line_t, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) { | ||||
|         // REM - comment line
 | ||||
|         return true; | ||||
|     } else if(strncmp(line_t, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) { | ||||
|         // DELAY
 | ||||
|         line_t = &line_t[args_get_first_word_length(line) + 1]; | ||||
|         uint32_t delay_val = 0; | ||||
|         state = ducky_get_delay_val(line_t, &delay_val); | ||||
|         if((state) && (delay_val > 0)) { | ||||
|             // Using ThreadFlagsWait as delay function allows exiting task on WorkerCmdStop command
 | ||||
|             if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, delay_val) == | ||||
|                WorkerCmdStop) | ||||
|                 return true; | ||||
|         } | ||||
|         return state; | ||||
|     } else if( | ||||
|         (strncmp(line_t, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || | ||||
|         (strncmp(line_t, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) { | ||||
|         // DEFAULT_DELAY
 | ||||
|         line_t = &line_t[args_get_first_word_length(line) + 1]; | ||||
|         return ducky_get_delay_val(line_t, &app->defdelay); | ||||
|     } else if(strncmp(line_t, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { | ||||
|         // STRING
 | ||||
|         if(app->defdelay > 0) { | ||||
|             if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, app->defdelay) == | ||||
|                WorkerCmdStop) | ||||
|                 return true; | ||||
|         } | ||||
|         line_t = &line_t[args_get_first_word_length(line) + 1]; | ||||
|         return ducky_string(line_t); | ||||
|     } else { | ||||
|         // Special keys + modifiers
 | ||||
|         uint16_t key = ducky_get_keycode(line_t, false); | ||||
|         if(key == KEY_NONE) return false; | ||||
|         if((key & 0xFF00) != 0) { | ||||
|             // It's a modifier key
 | ||||
|             line_t = &line_t[args_get_first_word_length(line) + 1]; | ||||
|             key |= ducky_get_keycode(line_t, true); | ||||
|         } | ||||
|         if(app->defdelay > 0) { | ||||
|             if(osThreadFlagsWait(WorkerCmdStop, osFlagsWaitAny | osFlagsNoClear, app->defdelay) == | ||||
|                WorkerCmdStop) | ||||
|                 return true; | ||||
|         } | ||||
|         furi_hal_hid_kb_press(key); | ||||
|         furi_hal_hid_kb_release(key); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| static void badusb_worker(void* context) { | ||||
|     BadUsbParams* app = context; | ||||
|     FURI_LOG_I("BadUSB worker", "Init"); | ||||
|     File* script_file = storage_file_alloc(furi_record_open("storage")); | ||||
|     BadUsbEvent evt; | ||||
|     string_t line; | ||||
|     uint32_t line_cnt = 0; | ||||
|     string_init(line); | ||||
|     if(storage_file_open(script_file, "/ext/badusb.txt", FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         char buffer[16]; | ||||
|         uint16_t ret; | ||||
|         uint32_t flags = | ||||
|             osThreadFlagsWait(WorkerCmdStart | WorkerCmdStop, osFlagsWaitAny, osWaitForever); | ||||
|         if(flags & WorkerCmdStart) { | ||||
|             FURI_LOG_I("BadUSB worker", "Start"); | ||||
|             do { | ||||
|                 ret = storage_file_read(script_file, buffer, 16); | ||||
|                 for(uint16_t i = 0; i < ret; i++) { | ||||
|                     if(buffer[i] == '\n' && string_size(line) > 0) { | ||||
|                         line_cnt++; | ||||
|                         if(ducky_parse_line(line, app) == false) { | ||||
|                             ret = 0; | ||||
|                             FURI_LOG_E("BadUSB worker", "Unknown command at line %lu", line_cnt); | ||||
|                             evt.type = EventTypeWorkerState; | ||||
|                             evt.worker.state = WorkerStateScriptError; | ||||
|                             evt.worker.line = line_cnt; | ||||
|                             osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); | ||||
|                             break; | ||||
|                         } | ||||
|                         flags = osThreadFlagsGet(); | ||||
|                         if(flags == WorkerCmdStop) { | ||||
|                             ret = 0; | ||||
|                             break; | ||||
|                         } | ||||
|                         string_clean(line); | ||||
|                     } else { | ||||
|                         string_push_back(line, buffer[i]); | ||||
|                     } | ||||
|                 } | ||||
|             } while(ret > 0); | ||||
|         } | ||||
|     } else { | ||||
|         FURI_LOG_E("BadUSB worker", "Script file open error"); | ||||
|         evt.type = EventTypeWorkerState; | ||||
|         evt.worker.state = WorkerStateNoFile; | ||||
|         osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); | ||||
|     } | ||||
|     string_clean(line); | ||||
|     string_clear(line); | ||||
| 
 | ||||
|     furi_hal_hid_kb_release_all(); | ||||
|     storage_file_close(script_file); | ||||
|     storage_file_free(script_file); | ||||
| 
 | ||||
|     FURI_LOG_I("BadUSB worker", "End"); | ||||
|     evt.type = EventTypeWorkerState; | ||||
|     evt.worker.state = WorkerStateDone; | ||||
|     osMessageQueuePut(app->event_queue, &evt, 0, osWaitForever); | ||||
| 
 | ||||
|     osThreadExit(); | ||||
| } | ||||
| 
 | ||||
| static void bad_usb_render_callback(Canvas* canvas, void* ctx) { | ||||
|     BadUsbParams* app = (BadUsbParams*)ctx; | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 0, 10, "Bad USB test"); | ||||
| 
 | ||||
|     if(strlen(app->msg_text) > 0) { | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         canvas_draw_str(canvas, 0, 62, app->msg_text); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void bad_usb_input_callback(InputEvent* input_event, void* ctx) { | ||||
|     osMessageQueueId_t event_queue = ctx; | ||||
| 
 | ||||
|     BadUsbEvent event; | ||||
|     event.type = EventTypeInput; | ||||
|     event.input = *input_event; | ||||
|     osMessageQueuePut(event_queue, &event, 0, osWaitForever); | ||||
| } | ||||
| 
 | ||||
| int32_t bad_usb_app(void* p) { | ||||
|     BadUsbParams* app = furi_alloc(sizeof(BadUsbParams)); | ||||
|     app->event_queue = osMessageQueueNew(8, sizeof(BadUsbEvent), NULL); | ||||
|     furi_check(app->event_queue); | ||||
|     ViewPort* view_port = view_port_alloc(); | ||||
| 
 | ||||
|     UsbMode usb_mode_prev = furi_hal_usb_get_config(); | ||||
|     furi_hal_usb_set_config(UsbModeHid); | ||||
| 
 | ||||
|     view_port_draw_callback_set(view_port, bad_usb_render_callback, app); | ||||
|     view_port_input_callback_set(view_port, bad_usb_input_callback, app->event_queue); | ||||
| 
 | ||||
|     // Open GUI and register view_port
 | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     gui_add_view_port(gui, view_port, GuiLayerFullscreen); | ||||
| 
 | ||||
|     app->thread = NULL; | ||||
|     app->thread_attr.name = "bad_usb_worker"; | ||||
|     app->thread_attr.stack_size = 2048; | ||||
|     app->thread = osThreadNew(badusb_worker, app, &app->thread_attr); | ||||
|     bool worker_running = true; | ||||
|     AppState app_state = AppStateWait; | ||||
|     snprintf(app->msg_text, sizeof(app->msg_text), "Press [OK] to start"); | ||||
|     view_port_update(view_port); | ||||
| 
 | ||||
|     BadUsbEvent event; | ||||
|     while(1) { | ||||
|         osStatus_t event_status = osMessageQueueGet(app->event_queue, &event, NULL, osWaitForever); | ||||
| 
 | ||||
|         if(event_status == osOK) { | ||||
|             if(event.type == EventTypeInput) { | ||||
|                 if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { | ||||
|                     if(worker_running) { | ||||
|                         osThreadFlagsSet(app->thread, WorkerCmdStop); | ||||
|                         app_state = AppStateExit; | ||||
|                     } else | ||||
|                         break; | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.type == InputTypeShort && event.input.key == InputKeyOk) { | ||||
|                     if(worker_running) { | ||||
|                         app_state = AppStateRunning; | ||||
|                         osThreadFlagsSet(app->thread, WorkerCmdStart); | ||||
|                         snprintf(app->msg_text, sizeof(app->msg_text), "Running..."); | ||||
|                         view_port_update(view_port); | ||||
|                     } | ||||
|                 } | ||||
|             } else if(event.type == EventTypeWorkerState) { | ||||
|                 FURI_LOG_I("BadUSB app", "ev: %d", event.worker.state); | ||||
|                 if(event.worker.state == WorkerStateDone) { | ||||
|                     worker_running = false; | ||||
|                     if(app_state == AppStateExit) | ||||
|                         break; | ||||
|                     else if(app_state == AppStateRunning) { | ||||
|                         //done
 | ||||
|                         app->thread = osThreadNew(badusb_worker, app, &app->thread_attr); | ||||
|                         worker_running = true; | ||||
|                         app_state = AppStateWait; | ||||
|                         snprintf(app->msg_text, sizeof(app->msg_text), "Press [OK] to start"); | ||||
|                         view_port_update(view_port); | ||||
|                     } | ||||
|                 } else if(event.worker.state == WorkerStateNoFile) { | ||||
|                     app_state = AppStateError; | ||||
|                     snprintf(app->msg_text, sizeof(app->msg_text), "File not found!"); | ||||
|                     view_port_update(view_port); | ||||
|                 } else if(event.worker.state == WorkerStateScriptError) { | ||||
|                     app_state = AppStateError; | ||||
|                     snprintf( | ||||
|                         app->msg_text, | ||||
|                         sizeof(app->msg_text), | ||||
|                         "Error at line %u", | ||||
|                         event.worker.line); | ||||
|                     view_port_update(view_port); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     furi_hal_usb_set_config(usb_mode_prev); | ||||
| 
 | ||||
|     // remove & free all stuff created by app
 | ||||
|     gui_remove_view_port(gui, view_port); | ||||
|     view_port_free(view_port); | ||||
| 
 | ||||
|     osMessageQueueDelete(app->event_queue); | ||||
|     free(app); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										121
									
								
								applications/debug_tools/usb_mouse.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								applications/debug_tools/usb_mouse.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include <gui/gui.h> | ||||
| #include <input/input.h> | ||||
| 
 | ||||
| #define MOUSE_MOVE_SHORT 5 | ||||
| #define MOUSE_MOVE_LONG 20 | ||||
| 
 | ||||
| typedef enum { | ||||
|     EventTypeInput, | ||||
| } EventType; | ||||
| 
 | ||||
| typedef struct { | ||||
|     union { | ||||
|         InputEvent input; | ||||
|     }; | ||||
|     EventType type; | ||||
| } UsbMouseEvent; | ||||
| 
 | ||||
| static void usb_mouse_render_callback(Canvas* canvas, void* ctx) { | ||||
|     canvas_clear(canvas); | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 0, 10, "USB Mouse demo"); | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); | ||||
| } | ||||
| 
 | ||||
| static void usb_mouse_input_callback(InputEvent* input_event, void* ctx) { | ||||
|     osMessageQueueId_t event_queue = ctx; | ||||
| 
 | ||||
|     UsbMouseEvent event; | ||||
|     event.type = EventTypeInput; | ||||
|     event.input = *input_event; | ||||
|     osMessageQueuePut(event_queue, &event, 0, osWaitForever); | ||||
| } | ||||
| 
 | ||||
| int32_t usb_mouse_app(void* p) { | ||||
|     osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(UsbMouseEvent), NULL); | ||||
|     furi_check(event_queue); | ||||
|     ViewPort* view_port = view_port_alloc(); | ||||
| 
 | ||||
|     UsbMode usb_mode_prev = furi_hal_usb_get_config(); | ||||
|     furi_hal_usb_set_config(UsbModeHid); | ||||
| 
 | ||||
|     view_port_draw_callback_set(view_port, usb_mouse_render_callback, NULL); | ||||
|     view_port_input_callback_set(view_port, usb_mouse_input_callback, event_queue); | ||||
| 
 | ||||
|     // Open GUI and register view_port
 | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     gui_add_view_port(gui, view_port, GuiLayerFullscreen); | ||||
| 
 | ||||
|     UsbMouseEvent event; | ||||
|     while(1) { | ||||
|         osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); | ||||
| 
 | ||||
|         if(event_status == osOK) { | ||||
|             if(event.type == EventTypeInput) { | ||||
|                 if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { | ||||
|                     furi_hal_hid_mouse_press(HID_MOUSE_BTN_RIGHT); | ||||
|                     furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.key == InputKeyOk) { | ||||
|                     if(event.input.type == InputTypePress) { | ||||
|                         furi_hal_hid_mouse_press(HID_MOUSE_BTN_LEFT); | ||||
|                     } else if(event.input.type == InputTypeRelease) { | ||||
|                         furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.key == InputKeyRight) { | ||||
|                     if(event.input.type == InputTypePress) { | ||||
|                         furi_hal_hid_mouse_move(MOUSE_MOVE_SHORT, 0); | ||||
|                     } else if(event.input.type == InputTypeRepeat) { | ||||
|                         furi_hal_hid_mouse_move(MOUSE_MOVE_LONG, 0); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.key == InputKeyLeft) { | ||||
|                     if(event.input.type == InputTypePress) { | ||||
|                         furi_hal_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); | ||||
|                     } else if(event.input.type == InputTypeRepeat) { | ||||
|                         furi_hal_hid_mouse_move(-MOUSE_MOVE_LONG, 0); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.key == InputKeyDown) { | ||||
|                     if(event.input.type == InputTypePress) { | ||||
|                         furi_hal_hid_mouse_move(0, MOUSE_MOVE_SHORT); | ||||
|                     } else if(event.input.type == InputTypeRepeat) { | ||||
|                         furi_hal_hid_mouse_move(0, MOUSE_MOVE_LONG); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if(event.input.key == InputKeyUp) { | ||||
|                     if(event.input.type == InputTypePress) { | ||||
|                         furi_hal_hid_mouse_move(0, -MOUSE_MOVE_SHORT); | ||||
|                     } else if(event.input.type == InputTypeRepeat) { | ||||
|                         furi_hal_hid_mouse_move(0, -MOUSE_MOVE_LONG); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         view_port_update(view_port); | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_usb_set_config(usb_mode_prev); | ||||
| 
 | ||||
|     // remove & free all stuff created by app
 | ||||
|     gui_remove_view_port(gui, view_port); | ||||
|     view_port_free(view_port); | ||||
|     osMessageQueueDelete(event_queue); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										103
									
								
								applications/debug_tools/usb_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								applications/debug_tools/usb_test.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/gui.h> | ||||
| #include <cmsis_os.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     Gui* gui; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     Submenu* submenu; | ||||
| } UsbTestApp; | ||||
| 
 | ||||
| typedef enum { | ||||
|     UsbTestSubmenuIndexEnable, | ||||
|     UsbTestSubmenuIndexDisable, | ||||
|     UsbTestSubmenuIndexVcpSingle, | ||||
|     UsbTestSubmenuIndexVcpDual, | ||||
|     UsbTestSubmenuIndexHid, | ||||
|     UsbTestSubmenuIndexHidU2F, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| void usb_test_submenu_callback(void* context, uint32_t index) { | ||||
|     furi_assert(context); | ||||
|     //UsbTestApp* app = context;
 | ||||
|     if(index == UsbTestSubmenuIndexEnable) { | ||||
|         furi_hal_usb_enable(); | ||||
|     } else if(index == UsbTestSubmenuIndexDisable) { | ||||
|         furi_hal_usb_disable(); | ||||
|     } else if(index == UsbTestSubmenuIndexVcpSingle) { | ||||
|         furi_hal_usb_set_config(UsbModeVcpSingle); | ||||
|     } else if(index == UsbTestSubmenuIndexVcpDual) { | ||||
|         furi_hal_usb_set_config(UsbModeVcpDual); | ||||
|     } else if(index == UsbTestSubmenuIndexHid) { | ||||
|         furi_hal_usb_set_config(UsbModeHid); | ||||
|     } else if(index == UsbTestSubmenuIndexHidU2F) { | ||||
|         //furi_hal_usb_set_config(UsbModeU2F);
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| uint32_t usb_test_exit(void* context) { | ||||
|     return VIEW_NONE; | ||||
| } | ||||
| 
 | ||||
| UsbTestApp* usb_test_app_alloc() { | ||||
|     UsbTestApp* app = furi_alloc(sizeof(UsbTestApp)); | ||||
| 
 | ||||
|     // Gui
 | ||||
|     app->gui = furi_record_open("gui"); | ||||
| 
 | ||||
|     // View dispatcher
 | ||||
|     app->view_dispatcher = view_dispatcher_alloc(); | ||||
|     view_dispatcher_enable_queue(app->view_dispatcher); | ||||
|     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||
| 
 | ||||
|     // Views
 | ||||
|     app->submenu = submenu_alloc(); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Enable", UsbTestSubmenuIndexEnable, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Disable", UsbTestSubmenuIndexDisable, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Single VCP", UsbTestSubmenuIndexVcpSingle, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Dual VCP", UsbTestSubmenuIndexVcpDual, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "HID KB+Mouse", UsbTestSubmenuIndexHid, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "TODO: HID U2F", UsbTestSubmenuIndexHidU2F, usb_test_submenu_callback, app); | ||||
|     view_set_previous_callback(submenu_get_view(app->submenu), usb_test_exit); | ||||
|     view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu)); | ||||
| 
 | ||||
|     // Switch to menu
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, 0); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| void usb_test_app_free(UsbTestApp* app) { | ||||
|     furi_assert(app); | ||||
| 
 | ||||
|     // Free views
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, 0); | ||||
|     submenu_free(app->submenu); | ||||
|     view_dispatcher_free(app->view_dispatcher); | ||||
| 
 | ||||
|     // Close gui record
 | ||||
|     furi_record_close("gui"); | ||||
|     app->gui = NULL; | ||||
| 
 | ||||
|     // Free rest
 | ||||
|     free(app); | ||||
| } | ||||
| 
 | ||||
| int32_t usb_test_app(void* p) { | ||||
|     UsbTestApp* app = usb_test_app_alloc(); | ||||
| 
 | ||||
|     view_dispatcher_run(app->view_dispatcher); | ||||
| 
 | ||||
|     usb_test_app_free(app); | ||||
|     return 0; | ||||
| } | ||||
| @ -40,7 +40,7 @@ Desktop* desktop_alloc() { | ||||
|     desktop->locked_view = desktop_locked_alloc(); | ||||
|     desktop->debug_view = desktop_debug_alloc(); | ||||
|     desktop->first_start_view = desktop_first_start_alloc(); | ||||
|     desktop->hw_mismatch_view = desktop_hw_mismatch_alloc(); | ||||
|     desktop->hw_mismatch_popup = popup_alloc(); | ||||
| 
 | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); | ||||
| @ -61,7 +61,7 @@ Desktop* desktop_alloc() { | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewHwMismatch, | ||||
|         desktop_hw_mismatch_get_view(desktop->hw_mismatch_view)); | ||||
|         popup_get_view(desktop->hw_mismatch_popup)); | ||||
| 
 | ||||
|     // Lock icon
 | ||||
|     desktop->lock_viewport = view_port_alloc(); | ||||
| @ -91,7 +91,7 @@ void desktop_free(Desktop* desktop) { | ||||
|     desktop_locked_free(desktop->locked_view); | ||||
|     desktop_debug_free(desktop->debug_view); | ||||
|     desktop_first_start_free(desktop->first_start_view); | ||||
|     desktop_hw_mismatch_free(desktop->hw_mismatch_view); | ||||
|     popup_free(desktop->hw_mismatch_popup); | ||||
| 
 | ||||
|     furi_record_close("gui"); | ||||
|     desktop->gui = NULL; | ||||
|  | ||||
| @ -7,24 +7,21 @@ | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/scene_manager.h> | ||||
| #include <assets_icons.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include "views/desktop_main.h" | ||||
| #include "views/desktop_first_start.h" | ||||
| #include "views/desktop_hw_mismatch.h" | ||||
| #include "views/desktop_lock_menu.h" | ||||
| #include "views/desktop_locked.h" | ||||
| #include "views/desktop_debug.h" | ||||
| 
 | ||||
| #include "scenes/desktop_scene.h" | ||||
| 
 | ||||
| #include "helpers/desktop_animation.h" | ||||
| #include "desktop/desktop_settings/desktop_settings.h" | ||||
| 
 | ||||
| #define HINT_TIMEOUT_L 2 | ||||
| #define HINT_TIMEOUT_H 11 | ||||
| 
 | ||||
| typedef enum { | ||||
|     DesktopViewMain, | ||||
|     DesktopViewLockMenu, | ||||
| @ -44,7 +41,7 @@ struct Desktop { | ||||
|     SceneManager* scene_manager; | ||||
| 
 | ||||
|     DesktopFirstStartView* first_start_view; | ||||
|     DesktopHwMismatchView* hw_mismatch_view; | ||||
|     Popup* hw_mismatch_popup; | ||||
|     DesktopMainView* main_view; | ||||
|     DesktopLockMenuView* lock_menu; | ||||
|     DesktopLockedView* locked_view; | ||||
|  | ||||
							
								
								
									
										26
									
								
								applications/desktop/helpers/desktop_animation.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								applications/desktop/helpers/desktop_animation.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| #include "desktop_animation.h" | ||||
| 
 | ||||
| static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||
| 
 | ||||
| const Icon* desktop_get_icon() { | ||||
|     uint8_t new = 0; | ||||
| 
 | ||||
| #if 0 | ||||
|     // checking dolphin state here to choose appropriate animation
 | ||||
| 
 | ||||
|     Dolphin* dolphin = furi_record_open("dolphin"); | ||||
|     DolphinStats stats = dolphin_stats(dolphin); | ||||
|     float timediff = fabs(difftime(stats.timestamp, dolphin_state_timestamp())); | ||||
| 
 | ||||
|     FURI_LOG_I("desktop-animation", "background change"); | ||||
|     FURI_LOG_I("desktop-animation", "icounter: %d", stats.icounter); | ||||
|     FURI_LOG_I("desktop-animation", "butthurt: %d", stats.butthurt); | ||||
|     FURI_LOG_I("desktop-animation", "time since deeed: %.0f", timediff); | ||||
| #endif | ||||
| 
 | ||||
|     if((random() % 100) > 50) { // temp rnd selection
 | ||||
|         new = random() % COUNT_OF(idle_scenes); | ||||
|     } | ||||
| 
 | ||||
|     return idle_scenes[new]; | ||||
| } | ||||
							
								
								
									
										10
									
								
								applications/desktop/helpers/desktop_animation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								applications/desktop/helpers/desktop_animation.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <math.h> | ||||
| #include <assets_icons.h> | ||||
| #include "dolphin/dolphin.h" | ||||
| #include "dolphin/helpers/dolphin_state.h" | ||||
| #include "time.h" | ||||
| 
 | ||||
| const Icon* desktop_get_icon(); | ||||
| @ -26,7 +26,7 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { | ||||
|         switch(event.event) { | ||||
|         case DesktopDebugEventExit: | ||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||
|             dolphin_save(dolphin); | ||||
|             dolphin_flush(dolphin); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
| @ -43,7 +43,7 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { | ||||
|             break; | ||||
| 
 | ||||
|         case DesktopDebugEventSaveState: | ||||
|             dolphin_save(dolphin); | ||||
|             dolphin_flush(dolphin); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|  | ||||
| @ -1,16 +1,26 @@ | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_hw_mismatch.h" | ||||
| #include <furi-hal-version.h> | ||||
| 
 | ||||
| void desktop_scene_hw_mismatch_callback(DesktopHwMismatchEvent event, void* context) { | ||||
| #define HW_MISMATCH_BACK_EVENT (0UL) | ||||
| 
 | ||||
| void desktop_scene_hw_mismatch_callback(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, HW_MISMATCH_BACK_EVENT); | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_hw_mismatch_on_enter(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
| 
 | ||||
|     desktop_hw_mismatch_set_callback( | ||||
|         desktop->hw_mismatch_view, desktop_scene_hw_mismatch_callback, desktop); | ||||
|     Popup* popup = desktop->hw_mismatch_popup; | ||||
|     char buffer[256]; // strange but smaller buffer not making it
 | ||||
|     snprintf( | ||||
|         buffer, | ||||
|         sizeof(buffer), | ||||
|         "HW target: F%d\nFW target: " TARGET, | ||||
|         furi_hal_version_get_hw_target()); | ||||
|     popup_set_context(popup, desktop); | ||||
|     popup_set_header(popup, "!!!! HW Mismatch !!!!", 60, 14, AlignCenter, AlignCenter); | ||||
|     popup_set_text(popup, buffer, 60, 37, AlignCenter, AlignCenter); | ||||
|     popup_set_callback(popup, desktop_scene_hw_mismatch_callback); | ||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||
| } | ||||
| 
 | ||||
| @ -20,7 +30,7 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DesktopHwMismatchEventExit: | ||||
|         case HW_MISMATCH_BACK_EVENT: | ||||
|             scene_manager_previous_scene(desktop->scene_manager); | ||||
|             consumed = true; | ||||
|             break; | ||||
| @ -33,5 +43,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_hw_mismatch_on_exit(void* context) { | ||||
|     // Desktop* desktop = (Desktop*)context;
 | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     Popup* popup = desktop->hw_mismatch_popup; | ||||
|     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); | ||||
|     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     popup_set_callback(popup, NULL); | ||||
|     popup_set_context(popup, NULL); | ||||
| } | ||||
|  | ||||
| @ -13,6 +13,7 @@ void desktop_scene_locked_on_enter(void* context) { | ||||
|     desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop); | ||||
|     desktop_locked_reset_door_pos(locked_view); | ||||
|     desktop_locked_update_hint_timeout(locked_view); | ||||
|     desktop_locked_set_dolphin_animation(locked_view); | ||||
| 
 | ||||
|     view_port_enabled_set(desktop->lock_viewport, true); | ||||
|     osTimerStart(locked_view->timer, 63); | ||||
| @ -45,7 +46,6 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
| void desktop_scene_locked_on_exit(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     DesktopLockedView* locked_view = desktop->locked_view; | ||||
|     desktop_locked_reset_counter(desktop->locked_view); | ||||
|     osTimerStop(locked_view->timer); | ||||
|     osTimerStop(desktop->locked_view->timer); | ||||
| } | ||||
|  | ||||
| @ -39,6 +39,7 @@ void desktop_scene_main_on_enter(void* context) { | ||||
|         desktop_main_unlocked(desktop->main_view); | ||||
|     } | ||||
| 
 | ||||
|     desktop_main_switch_dolphin_animation(desktop->main_view); | ||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); | ||||
| } | ||||
| 
 | ||||
| @ -59,7 +60,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|             break; | ||||
| 
 | ||||
|         case DesktopMainEventOpenDebug: | ||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopViewDebug); | ||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneDebug); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
| @ -67,6 +68,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case DesktopMainEventOpenFavorite: | ||||
|             desktop_settings_load(&desktop->settings); | ||||
|             desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); | ||||
|  | ||||
| @ -2,8 +2,8 @@ | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_debug.h" | ||||
| 
 | ||||
| #include "applications/dolphin/helpers/dolphin_state.h" | ||||
| #include "applications/dolphin/dolphin.h" | ||||
| #include "dolphin/helpers/dolphin_state.h" | ||||
| #include "dolphin/dolphin.h" | ||||
| 
 | ||||
| void desktop_debug_set_callback( | ||||
|     DesktopDebugView* debug_view, | ||||
| @ -21,7 +21,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|     const Version* ver; | ||||
|     char buffer[64]; | ||||
| 
 | ||||
|     static const char* headers[] = {"FW Version info:", "Boot Version info:", "Desktop info:"}; | ||||
|     static const char* headers[] = {"FW Version info:", "Boot Version info:", "Dolphin info:"}; | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
| @ -56,7 +56,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|             "%s [%s]", | ||||
|             version_get_version(ver), | ||||
|             version_get_builddate(ver)); | ||||
|         canvas_draw_str(canvas, 5, 33, buffer); | ||||
|         canvas_draw_str(canvas, 5, 32, buffer); | ||||
| 
 | ||||
|         snprintf( | ||||
|             buffer, | ||||
| @ -68,16 +68,22 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
| 
 | ||||
|         snprintf( | ||||
|             buffer, sizeof(buffer), "[%s] %s", version_get_target(ver), version_get_gitbranch(ver)); | ||||
|         canvas_draw_str(canvas, 5, 53, buffer); | ||||
|         canvas_draw_str(canvas, 5, 54, buffer); | ||||
| 
 | ||||
|     } else { | ||||
|         char buffer[64]; | ||||
|         uint32_t current_lvl = dolphin_state_get_level(m->icounter); | ||||
|         uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter, current_lvl, true); | ||||
| 
 | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         snprintf(buffer, 64, "Icounter: %ld", m->icounter); | ||||
|         canvas_draw_str(canvas, 5, 30, buffer); | ||||
|         snprintf(buffer, 64, "Butthurt: %ld", m->butthurt); | ||||
|         canvas_draw_str(canvas, 5, 40, buffer); | ||||
|         snprintf(buffer, 64, "Icounter: %ld  Butthurt %ld", m->icounter, m->butthurt); | ||||
|         canvas_draw_str(canvas, 5, 23, buffer); | ||||
| 
 | ||||
|         snprintf(buffer, 64, "Level: %ld  To level up: %ld", current_lvl, remaining); | ||||
|         canvas_draw_str(canvas, 5, 33, buffer); | ||||
| 
 | ||||
|         snprintf(buffer, 64, "%s", asctime(localtime((const time_t*)&m->timestamp))); | ||||
|         canvas_draw_str(canvas, 5, 43, buffer); | ||||
|         canvas_draw_str(canvas, 0, 53, "[< >] icounter value   [ok] save"); | ||||
|     } | ||||
| } | ||||
| @ -146,11 +152,12 @@ void desktop_debug_free(DesktopDebugView* debug_view) { | ||||
| 
 | ||||
| void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) { | ||||
|     Dolphin* dolphin = furi_record_open("dolphin"); | ||||
|     DolphinDeedWeight stats = dolphin_stats(dolphin); | ||||
|     DolphinStats stats = dolphin_stats(dolphin); | ||||
|     with_view_model( | ||||
|         debug_view->view, (DesktopDebugViewModel * model) { | ||||
|             model->icounter = stats.icounter; | ||||
|             model->butthurt = stats.butthurt; | ||||
|             model->timestamp = stats.timestamp; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <storage/storage.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     DesktopDebugEventDeed, | ||||
| @ -35,6 +36,7 @@ struct DesktopDebugView { | ||||
| typedef struct { | ||||
|     uint32_t icounter; | ||||
|     uint32_t butthurt; | ||||
|     uint64_t timestamp; | ||||
|     DesktopViewStatsScreens screen; | ||||
| } DesktopDebugViewModel; | ||||
| 
 | ||||
|  | ||||
| @ -1,66 +0,0 @@ | ||||
| #include <furi.h> | ||||
| #include "../desktop_i.h" | ||||
| #include <furi-hal.h> | ||||
| #include <furi-hal-version.h> | ||||
| 
 | ||||
| #include "desktop_hw_mismatch.h" | ||||
| 
 | ||||
| void desktop_hw_mismatch_set_callback( | ||||
|     DesktopHwMismatchView* main_view, | ||||
|     DesktopHwMismatchViewCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(main_view); | ||||
|     furi_assert(callback); | ||||
|     main_view->callback = callback; | ||||
|     main_view->context = context; | ||||
| } | ||||
| 
 | ||||
| void desktop_hw_mismatch_render(Canvas* canvas, void* model) { | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 2, 15, "!!!! HW Mismatch !!!!"); | ||||
| 
 | ||||
|     char buffer[64]; | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     snprintf(buffer, 64, "HW target: F%d", furi_hal_version_get_hw_target()); | ||||
|     canvas_draw_str(canvas, 5, 27, buffer); | ||||
|     canvas_draw_str(canvas, 5, 38, "FW target: " TARGET); | ||||
| } | ||||
| 
 | ||||
| View* desktop_hw_mismatch_get_view(DesktopHwMismatchView* hw_mismatch_view) { | ||||
|     furi_assert(hw_mismatch_view); | ||||
|     return hw_mismatch_view->view; | ||||
| } | ||||
| 
 | ||||
| bool desktop_hw_mismatch_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     DesktopHwMismatchView* hw_mismatch_view = context; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         hw_mismatch_view->callback(DesktopHwMismatchEventExit, hw_mismatch_view->context); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| DesktopHwMismatchView* desktop_hw_mismatch_alloc() { | ||||
|     DesktopHwMismatchView* hw_mismatch_view = furi_alloc(sizeof(DesktopHwMismatchView)); | ||||
|     hw_mismatch_view->view = view_alloc(); | ||||
|     view_allocate_model( | ||||
|         hw_mismatch_view->view, ViewModelTypeLocking, sizeof(DesktopHwMismatchViewModel)); | ||||
|     view_set_context(hw_mismatch_view->view, hw_mismatch_view); | ||||
|     view_set_draw_callback(hw_mismatch_view->view, (ViewDrawCallback)desktop_hw_mismatch_render); | ||||
|     view_set_input_callback(hw_mismatch_view->view, desktop_hw_mismatch_input); | ||||
| 
 | ||||
|     return hw_mismatch_view; | ||||
| } | ||||
| 
 | ||||
| void desktop_hw_mismatch_free(DesktopHwMismatchView* hw_mismatch_view) { | ||||
|     furi_assert(hw_mismatch_view); | ||||
| 
 | ||||
|     view_free(hw_mismatch_view->view); | ||||
|     free(hw_mismatch_view); | ||||
| } | ||||
| @ -1,38 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     DesktopHwMismatchEventExit, | ||||
| } DesktopHwMismatchEvent; | ||||
| 
 | ||||
| typedef struct DesktopHwMismatchView DesktopHwMismatchView; | ||||
| 
 | ||||
| typedef void (*DesktopHwMismatchViewCallback)(DesktopHwMismatchEvent event, void* context); | ||||
| 
 | ||||
| struct DesktopHwMismatchView { | ||||
|     View* view; | ||||
|     DesktopHwMismatchViewCallback callback; | ||||
|     void* context; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     IconAnimation* animation; | ||||
|     uint8_t scene_num; | ||||
|     uint8_t hint_timeout; | ||||
|     bool locked; | ||||
| } DesktopHwMismatchViewModel; | ||||
| 
 | ||||
| void desktop_hw_mismatch_set_callback( | ||||
|     DesktopHwMismatchView* hw_mismatch_view, | ||||
|     DesktopHwMismatchViewCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| View* desktop_hw_mismatch_get_view(DesktopHwMismatchView* hw_mismatch_view); | ||||
| 
 | ||||
| DesktopHwMismatchView* desktop_hw_mismatch_alloc(); | ||||
| void desktop_hw_mismatch_free(DesktopHwMismatchView* hw_mismatch_view); | ||||
| @ -29,7 +29,7 @@ static void lock_menu_callback(void* context, uint8_t index) { | ||||
|     default: // wip message
 | ||||
|         with_view_model( | ||||
|             lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||
|                 model->hint_timeout = HINT_TIMEOUT_L; | ||||
|                 model->hint_timeout = HINT_TIMEOUT; | ||||
|                 return true; | ||||
|             }); | ||||
|         break; | ||||
|  | ||||
| @ -6,6 +6,8 @@ | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define HINT_TIMEOUT 2 | ||||
| 
 | ||||
| typedef enum { | ||||
|     DesktopLockMenuEventLock, | ||||
|     DesktopLockMenuEventUnlock, | ||||
| @ -25,7 +27,6 @@ struct DesktopLockMenuView { | ||||
| typedef struct { | ||||
|     uint8_t idx; | ||||
|     uint8_t hint_timeout; | ||||
|     bool locked; | ||||
| } DesktopLockMenuViewModel; | ||||
| 
 | ||||
| void desktop_lock_menu_set_callback( | ||||
|  | ||||
| @ -2,8 +2,6 @@ | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_locked.h" | ||||
| 
 | ||||
| static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||
| 
 | ||||
| void desktop_locked_set_callback( | ||||
|     DesktopLockedView* locked_view, | ||||
|     DesktopLockedViewCallback callback, | ||||
| @ -20,13 +18,12 @@ void locked_view_timer_callback(void* context) { | ||||
| } | ||||
| 
 | ||||
| // temporary locked screen animation managment
 | ||||
| static void | ||||
|     desktop_scene_handler_set_scene(DesktopLockedView* locked_view, const Icon* icon_data) { | ||||
| void desktop_locked_set_dolphin_animation(DesktopLockedView* locked_view) { | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             if(model->animation) icon_animation_free(model->animation); | ||||
|             model->animation = icon_animation_alloc(icon_data); | ||||
|             icon_animation_start(model->animation); | ||||
|             model->animation = icon_animation_alloc(desktop_get_icon()); | ||||
|             view_tie_icon_animation(locked_view->view, model->animation); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -34,7 +31,7 @@ static void | ||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             model->hint_timeout = HINT_TIMEOUT_H; | ||||
|             model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -43,8 +40,8 @@ void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             model->animation_seq_end = false; | ||||
|             model->door_left_x = -57; | ||||
|             model->door_right_x = 115; | ||||
|             model->door_left_x = DOOR_L_POS; | ||||
|             model->door_right_x = DOOR_R_POS; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -58,9 +55,12 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view) { | ||||
|             animation_seq_end = model->animation_seq_end; | ||||
| 
 | ||||
|             if(!model->animation_seq_end) { | ||||
|                 model->door_left_x = CLAMP(model->door_left_x + 5, 0, -57); | ||||
|                 model->door_right_x = CLAMP(model->door_right_x - 5, 115, 60); | ||||
|                 model->door_left_x = CLAMP(model->door_left_x + 5, DOOR_L_POS_MAX, DOOR_L_POS); | ||||
|                 model->door_right_x = CLAMP(model->door_right_x - 5, DOOR_R_POS, DOOR_R_POS_MIN); | ||||
|             } else { | ||||
|                 model->hint_expire_at = !model->hint_expire_at; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
| @ -75,14 +75,14 @@ void desktop_locked_reset_counter(DesktopLockedView* locked_view) { | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             model->hint_timeout = 0; | ||||
|             model->hint_expire_at = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_render(Canvas* canvas, void* model) { | ||||
|     DesktopLockedViewModel* m = model; | ||||
| 
 | ||||
|     uint32_t now = osKernelGetTickCount(); | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
| @ -95,12 +95,11 @@ void desktop_locked_render(Canvas* canvas, void* model) { | ||||
|         canvas_draw_icon_animation(canvas, 0, -3, m->animation); | ||||
|     } | ||||
| 
 | ||||
|     if(m->hint_timeout) { | ||||
|         m->hint_timeout--; | ||||
| 
 | ||||
|     if(now < m->hint_expire_at) { | ||||
|         if(!m->animation_seq_end) { | ||||
|             canvas_set_font(canvas, FontPrimary); | ||||
|             elements_multiline_text_framed(canvas, 42, 30, "Locked"); | ||||
| 
 | ||||
|         } else { | ||||
|             canvas_set_font(canvas, FontSecondary); | ||||
|             canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); | ||||
| @ -120,15 +119,10 @@ bool desktop_locked_input(InputEvent* event, void* context) { | ||||
| 
 | ||||
|     DesktopLockedView* locked_view = context; | ||||
|     if(event->type == InputTypeShort) { | ||||
|         with_view_model( | ||||
|             locked_view->view, (DesktopLockedViewModel * model) { | ||||
|                 model->hint_timeout = HINT_TIMEOUT_L; | ||||
|                 return true; | ||||
|             }); | ||||
|         desktop_locked_update_hint_timeout(locked_view); | ||||
| 
 | ||||
|         if(event->key == InputKeyBack) { | ||||
|             uint32_t press_time = HAL_GetTick(); | ||||
| 
 | ||||
|             uint32_t press_time = osKernelGetTickCount(); | ||||
|             // check if pressed sequentially
 | ||||
|             if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { | ||||
|                 locked_view->lock_lastpress = press_time; | ||||
| @ -148,6 +142,26 @@ bool desktop_locked_input(InputEvent* event, void* context) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_enter(void* context) { | ||||
|     DesktopLockedView* locked_view = context; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             if(model->animation) icon_animation_start(model->animation); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_exit(void* context) { | ||||
|     DesktopLockedView* locked_view = context; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             if(model->animation) icon_animation_stop(model->animation); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| DesktopLockedView* desktop_locked_alloc() { | ||||
|     DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView)); | ||||
|     locked_view->view = view_alloc(); | ||||
| @ -158,8 +172,9 @@ DesktopLockedView* desktop_locked_alloc() { | ||||
|     view_set_context(locked_view->view, locked_view); | ||||
|     view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_render); | ||||
|     view_set_input_callback(locked_view->view, desktop_locked_input); | ||||
|     view_set_enter_callback(locked_view->view, desktop_locked_enter); | ||||
|     view_set_exit_callback(locked_view->view, desktop_locked_exit); | ||||
| 
 | ||||
|     desktop_scene_handler_set_scene(locked_view, idle_scenes[random() % COUNT_OF(idle_scenes)]); | ||||
|     return locked_view; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -6,9 +6,14 @@ | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define UNLOCK_RST_TIMEOUT 200 | ||||
| #define UNLOCK_RST_TIMEOUT 300 | ||||
| #define UNLOCK_CNT 2 // 3 actually
 | ||||
| 
 | ||||
| #define DOOR_L_POS -57 | ||||
| #define DOOR_L_POS_MAX 0 | ||||
| #define DOOR_R_POS 115 | ||||
| #define DOOR_R_POS_MIN 60 | ||||
| 
 | ||||
| typedef enum { | ||||
|     DesktopLockedEventUnlock, | ||||
|     DesktopLockedEventUpdate, | ||||
| @ -30,10 +35,11 @@ struct DesktopLockedView { | ||||
| 
 | ||||
| typedef struct { | ||||
|     IconAnimation* animation; | ||||
|     uint32_t hint_expire_at; | ||||
| 
 | ||||
|     uint8_t scene_num; | ||||
|     int8_t door_left_x; | ||||
|     int8_t door_right_x; | ||||
|     uint8_t hint_timeout; | ||||
|     bool animation_seq_end; | ||||
| 
 | ||||
| } DesktopLockedViewModel; | ||||
| @ -43,6 +49,7 @@ void desktop_locked_set_callback( | ||||
|     DesktopLockedViewCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| void desktop_locked_set_dolphin_animation(DesktopLockedView* locked_view); | ||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); | ||||
| void desktop_locked_reset_counter(DesktopLockedView* locked_view); | ||||
| void desktop_locked_reset_door_pos(DesktopLockedView* locked_view); | ||||
|  | ||||
| @ -2,8 +2,6 @@ | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_main.h" | ||||
| 
 | ||||
| static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||
| 
 | ||||
| void desktop_main_set_callback( | ||||
|     DesktopMainView* main_view, | ||||
|     DesktopMainViewCallback callback, | ||||
| @ -17,30 +15,17 @@ void desktop_main_set_callback( | ||||
| void desktop_main_reset_hint(DesktopMainView* main_view) { | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             model->hint_timeout = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| // temporary main screen animation managment
 | ||||
| void desktop_scene_handler_set_scene(DesktopMainView* main_view, const Icon* icon_data) { | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             if(model->animation) icon_animation_free(model->animation); | ||||
|             model->animation = icon_animation_alloc(icon_data); | ||||
|             icon_animation_start(model->animation); | ||||
|             model->hint_expire_at = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_handler_switch_scene(DesktopMainView* main_view) { | ||||
| void desktop_main_switch_dolphin_animation(DesktopMainView* main_view) { | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             if(icon_animation_is_last_frame(model->animation)) { | ||||
|             if(model->animation) icon_animation_free(model->animation); | ||||
|                 model->animation = icon_animation_alloc(idle_scenes[model->scene_num]); | ||||
|                 icon_animation_start(model->animation); | ||||
|                 model->scene_num = random() % COUNT_OF(idle_scenes); | ||||
|             } | ||||
|             model->animation = icon_animation_alloc(desktop_get_icon()); | ||||
|             view_tie_icon_animation(main_view->view, model->animation); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -48,13 +33,13 @@ void desktop_scene_handler_switch_scene(DesktopMainView* main_view) { | ||||
| void desktop_main_render(Canvas* canvas, void* model) { | ||||
|     canvas_clear(canvas); | ||||
|     DesktopMainViewModel* m = model; | ||||
|     uint32_t now = osKernelGetTickCount(); | ||||
| 
 | ||||
|     if(m->animation) { | ||||
|         canvas_draw_icon_animation(canvas, 0, -3, m->animation); | ||||
|     } | ||||
| 
 | ||||
|     if(m->unlocked && m->hint_timeout) { | ||||
|         m->hint_timeout = CLAMP(m->hint_timeout - 1, 2, 0); | ||||
|     if(now < m->hint_expire_at) { | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         elements_multiline_text_framed(canvas, 42, 30, "Unlocked"); | ||||
|     } | ||||
| @ -87,6 +72,25 @@ bool desktop_main_input(InputEvent* event, void* context) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void desktop_main_enter(void* context) { | ||||
|     DesktopMainView* main_view = context; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             if(model->animation) icon_animation_start(model->animation); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void desktop_main_exit(void* context) { | ||||
|     DesktopMainView* main_view = context; | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             if(model->animation) icon_animation_stop(model->animation); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| DesktopMainView* desktop_main_alloc() { | ||||
|     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); | ||||
|     main_view->view = view_alloc(); | ||||
| @ -94,8 +98,8 @@ DesktopMainView* desktop_main_alloc() { | ||||
|     view_set_context(main_view->view, main_view); | ||||
|     view_set_draw_callback(main_view->view, (ViewDrawCallback)desktop_main_render); | ||||
|     view_set_input_callback(main_view->view, desktop_main_input); | ||||
| 
 | ||||
|     desktop_scene_handler_set_scene(main_view, idle_scenes[random() % COUNT_OF(idle_scenes)]); | ||||
|     view_set_enter_callback(main_view->view, desktop_main_enter); | ||||
|     view_set_exit_callback(main_view->view, desktop_main_exit); | ||||
| 
 | ||||
|     return main_view; | ||||
| } | ||||
| @ -109,8 +113,7 @@ void desktop_main_free(DesktopMainView* main_view) { | ||||
| void desktop_main_unlocked(DesktopMainView* main_view) { | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             model->unlocked = true; | ||||
|             model->hint_timeout = 2; | ||||
|             model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @ -28,8 +28,7 @@ struct DesktopMainView { | ||||
| typedef struct { | ||||
|     IconAnimation* animation; | ||||
|     uint8_t scene_num; | ||||
|     uint8_t hint_timeout; | ||||
|     bool unlocked; | ||||
|     uint32_t hint_expire_at; | ||||
| } DesktopMainViewModel; | ||||
| 
 | ||||
| void desktop_main_set_callback( | ||||
| @ -40,4 +39,7 @@ void desktop_main_set_callback( | ||||
| View* desktop_main_get_view(DesktopMainView* main_view); | ||||
| 
 | ||||
| DesktopMainView* desktop_main_alloc(); | ||||
| 
 | ||||
| void desktop_main_free(DesktopMainView* main_view); | ||||
| 
 | ||||
| void desktop_main_switch_dolphin_animation(DesktopMainView* main_view); | ||||
|  | ||||
| @ -60,3 +60,14 @@ DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage | ||||
| 
 | ||||
|     return return_data.dialog_value; | ||||
| } | ||||
| 
 | ||||
| /****************** Storage error ******************/ | ||||
| 
 | ||||
| void dialog_message_show_storage_error(DialogsApp* context, const char* error_text) { | ||||
|     DialogMessage* message = dialog_message_alloc(); | ||||
|     dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter); | ||||
|     dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6); | ||||
|     dialog_message_set_buttons(message, "Back", NULL, NULL); | ||||
|     dialog_message_show(context, message); | ||||
|     dialog_message_free(message); | ||||
| } | ||||
| @ -123,6 +123,13 @@ void dialog_message_set_buttons( | ||||
|  */ | ||||
| DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* message); | ||||
| 
 | ||||
| /**
 | ||||
|  * Show SD error message (with question sign) | ||||
|  * @param context  | ||||
|  * @param error_text  | ||||
|  */ | ||||
| void dialog_message_show_storage_error(DialogsApp* context, const char* error_text); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,34 +1,39 @@ | ||||
| #include "dolphin_i.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| bool dolphin_load(Dolphin* dolphin) { | ||||
|     furi_assert(dolphin); | ||||
|     return dolphin_state_load(dolphin->state); | ||||
| } | ||||
| 
 | ||||
| void dolphin_save(Dolphin* dolphin) { | ||||
|     furi_assert(dolphin); | ||||
|     DolphinEvent event; | ||||
|     event.type = DolphinEventTypeSave; | ||||
|     furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK); | ||||
| } | ||||
| #define DOLPHIN_TIMEGATE 86400 // one day
 | ||||
| #define DOLPHIN_LOCK_EVENT_FLAG (0x1) | ||||
| 
 | ||||
| void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { | ||||
|     furi_assert(dolphin); | ||||
|     DolphinEvent event; | ||||
|     event.type = DolphinEventTypeDeed; | ||||
|     event.deed = deed; | ||||
|     furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK); | ||||
|     dolphin_event_send_async(dolphin, &event); | ||||
| } | ||||
| 
 | ||||
| DolphinDeedWeight dolphin_stats(Dolphin* dolphin) { | ||||
|     DolphinDeedWeight stats; | ||||
|     stats.butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||
|     stats.icounter = dolphin_state_get_icounter(dolphin->state); | ||||
| DolphinStats dolphin_stats(Dolphin* dolphin) { | ||||
|     furi_assert(dolphin); | ||||
| 
 | ||||
|     DolphinStats stats; | ||||
|     DolphinEvent event; | ||||
| 
 | ||||
|     event.type = DolphinEventTypeStats; | ||||
|     event.stats = &stats; | ||||
| 
 | ||||
|     dolphin_event_send_wait(dolphin, &event); | ||||
| 
 | ||||
|     return stats; | ||||
| } | ||||
| 
 | ||||
| void dolphin_flush(Dolphin* dolphin) { | ||||
|     furi_assert(dolphin); | ||||
| 
 | ||||
|     DolphinEvent event; | ||||
|     event.type = DolphinEventTypeFlush; | ||||
| 
 | ||||
|     dolphin_event_send_wait(dolphin, &event); | ||||
| } | ||||
| 
 | ||||
| Dolphin* dolphin_alloc() { | ||||
|     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); | ||||
| 
 | ||||
| @ -47,27 +52,72 @@ void dolphin_free(Dolphin* dolphin) { | ||||
|     free(dolphin); | ||||
| } | ||||
| 
 | ||||
| void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { | ||||
|     furi_assert(dolphin); | ||||
|     furi_assert(event); | ||||
|     event->flag = NULL; | ||||
|     furi_check(osMessageQueuePut(dolphin->event_queue, event, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) { | ||||
|     furi_assert(dolphin); | ||||
|     furi_assert(event); | ||||
|     event->flag = osEventFlagsNew(NULL); | ||||
|     furi_check(event->flag); | ||||
|     furi_check(osMessageQueuePut(dolphin->event_queue, event, 0, osWaitForever) == osOK); | ||||
|     furi_check( | ||||
|         osEventFlagsWait(event->flag, DOLPHIN_LOCK_EVENT_FLAG, osFlagsWaitAny, osWaitForever) == | ||||
|         DOLPHIN_LOCK_EVENT_FLAG); | ||||
|     furi_check(osEventFlagsDelete(event->flag) == osOK); | ||||
| } | ||||
| 
 | ||||
| void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) { | ||||
|     if(event->flag) { | ||||
|         osEventFlagsSet(event->flag, DOLPHIN_LOCK_EVENT_FLAG); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void dolphin_check_butthurt(DolphinState* state) { | ||||
|     furi_assert(state); | ||||
|     float diff_time = difftime(dolphin_state_get_timestamp(state), dolphin_state_timestamp()); | ||||
| 
 | ||||
| #if 0 | ||||
|     FURI_LOG_I("dolphin-state", "Butthurt check, time since deed %.0f", fabs(diff_time)); | ||||
| #endif | ||||
| 
 | ||||
|     if((fabs(diff_time)) > DOLPHIN_TIMEGATE) { | ||||
|         // increase butthurt
 | ||||
|         FURI_LOG_I("dolphin-state", "Increasing butthurt"); | ||||
|         dolphin_state_butthurted(state); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int32_t dolphin_srv(void* p) { | ||||
|     Dolphin* dolphin = dolphin_alloc(); | ||||
|     furi_record_create("dolphin", dolphin); | ||||
| 
 | ||||
|     dolphin_state_load(dolphin->state); | ||||
| 
 | ||||
|     DolphinEvent event; | ||||
|     while(1) { | ||||
|         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); | ||||
|         switch(event.type) { | ||||
|         case DolphinEventTypeDeed: | ||||
|         if(osMessageQueueGet(dolphin->event_queue, &event, NULL, 60000) == osOK) { | ||||
|             if(event.type == DolphinEventTypeDeed) { | ||||
|                 dolphin_state_on_deed(dolphin->state, event.deed); | ||||
|             break; | ||||
| 
 | ||||
|         case DolphinEventTypeSave: | ||||
|             } else if(event.type == DolphinEventTypeStats) { | ||||
|                 event.stats->icounter = dolphin_state_get_icounter(dolphin->state); | ||||
|                 event.stats->butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||
|                 event.stats->timestamp = dolphin_state_get_timestamp(dolphin->state); | ||||
|             } else if(event.type == DolphinEventTypeFlush) { | ||||
|                 dolphin_state_save(dolphin->state); | ||||
|             } | ||||
|             dolphin_event_release(dolphin, &event); | ||||
|         } else { | ||||
|             dolphin_check_butthurt(dolphin->state); | ||||
|             dolphin_state_save(dolphin->state); | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     dolphin_free(dolphin); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -4,27 +4,24 @@ | ||||
| 
 | ||||
| typedef struct Dolphin Dolphin; | ||||
| 
 | ||||
| /* Load Dolphin state
 | ||||
|  * Thread safe | ||||
|  */ | ||||
| typedef struct { | ||||
|     uint32_t icounter; | ||||
|     uint32_t butthurt; | ||||
|     uint64_t timestamp; | ||||
| } DolphinStats; | ||||
| 
 | ||||
| bool dolphin_load(Dolphin* dolphin); | ||||
| 
 | ||||
| /* Deed complete notification. Call it on deed completion.
 | ||||
| /** Deed complete notification. Call it on deed completion.
 | ||||
|  * See dolphin_deed.h for available deeds. In futures it will become part of assets. | ||||
|  * Thread safe | ||||
|  * Thread safe, async | ||||
|  */ | ||||
| 
 | ||||
| void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); | ||||
| 
 | ||||
| /* Save Dolphin state (write to permanent memory)
 | ||||
|  * Thread safe | ||||
| /** Retrieve dolphin stats
 | ||||
|  * Thread safe, blocking | ||||
|  */ | ||||
| DolphinStats dolphin_stats(Dolphin* dolphin); | ||||
| 
 | ||||
| void dolphin_save(Dolphin* dolphin); | ||||
| 
 | ||||
| /* Retrieve dolphin's icounter and butthurt values
 | ||||
|  * Thread safe | ||||
| /** Flush dolphin queue and save state
 | ||||
|  * Thread safe, blocking | ||||
|  */ | ||||
| 
 | ||||
| DolphinDeedWeight dolphin_stats(Dolphin* dolphin); | ||||
| void dolphin_flush(Dolphin* dolphin); | ||||
| @ -8,14 +8,16 @@ | ||||
| 
 | ||||
| typedef enum { | ||||
|     DolphinEventTypeDeed, | ||||
|     DolphinEventTypeSave, | ||||
|     DolphinEventTypeTick, | ||||
|     DolphinEventTypeStats, | ||||
|     DolphinEventTypeFlush, | ||||
| } DolphinEventType; | ||||
| 
 | ||||
| typedef struct { | ||||
|     DolphinEventType type; | ||||
|     osEventFlagsId_t flag; | ||||
|     union { | ||||
|         DolphinDeed deed; | ||||
|         DolphinStats* stats; | ||||
|     }; | ||||
| } DolphinEvent; | ||||
| 
 | ||||
| @ -29,3 +31,9 @@ struct Dolphin { | ||||
| Dolphin* dolphin_alloc(); | ||||
| 
 | ||||
| void dolphin_free(Dolphin* dolphin); | ||||
| 
 | ||||
| void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); | ||||
| 
 | ||||
| void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); | ||||
| 
 | ||||
| void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event); | ||||
|  | ||||
| @ -25,6 +25,7 @@ typedef struct { | ||||
|     uint32_t flags; | ||||
|     uint32_t icounter; | ||||
|     uint32_t butthurt; | ||||
|     uint64_t timestamp; | ||||
| } DolphinStoreData; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -35,6 +36,7 @@ typedef struct { | ||||
| struct DolphinState { | ||||
|     Storage* fs_api; | ||||
|     DolphinStoreData data; | ||||
|     bool dirty; | ||||
| }; | ||||
| 
 | ||||
| DolphinState* dolphin_state_alloc() { | ||||
| @ -49,8 +51,12 @@ void dolphin_state_free(DolphinState* dolphin_state) { | ||||
| } | ||||
| 
 | ||||
| bool dolphin_state_save(DolphinState* dolphin_state) { | ||||
|     if(!dolphin_state->dirty) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_I("dolphin-state", "State is dirty, saving to \"%s\"", DOLPHIN_STORE_KEY); | ||||
|     DolphinStore store; | ||||
|     FURI_LOG_I("dolphin-state", "Saving state to \"%s\"", DOLPHIN_STORE_KEY); | ||||
|     // Calculate checksum
 | ||||
|     uint8_t* source = (uint8_t*)&dolphin_state->data; | ||||
|     uint8_t checksum = 0; | ||||
| @ -88,7 +94,10 @@ bool dolphin_state_save(DolphinState* dolphin_state) { | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
| 
 | ||||
|     dolphin_state->dirty = !save_result; | ||||
| 
 | ||||
|     FURI_LOG_I("dolphin-state", "Saved"); | ||||
| 
 | ||||
|     return save_result; | ||||
| } | ||||
| 
 | ||||
| @ -99,8 +108,12 @@ bool dolphin_state_load(DolphinState* dolphin_state) { | ||||
| 
 | ||||
|     File* file = storage_file_alloc(dolphin_state->fs_api); | ||||
|     bool load_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(load_result) { | ||||
|     if(!load_result) { | ||||
|         FURI_LOG_E( | ||||
|             "dolphin-state", | ||||
|             "Load failed. Storage returned: %s", | ||||
|             storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         uint16_t bytes_count = storage_file_read(file, &store, sizeof(DolphinStore)); | ||||
| 
 | ||||
|         if(bytes_count != sizeof(DolphinStore)) { | ||||
| @ -109,12 +122,8 @@ bool dolphin_state_load(DolphinState* dolphin_state) { | ||||
|     } | ||||
| 
 | ||||
|     if(!load_result) { | ||||
|         FURI_LOG_E( | ||||
|             "dolphin-state", | ||||
|             "Load failed. Storage returned: %s", | ||||
|             storage_file_get_error_desc(file)); | ||||
|         FURI_LOG_E("dolphin-state", "DolphinStore size mismatch"); | ||||
|     } else { | ||||
|         FURI_LOG_I("dolphin-state", "State loaded, verifying header"); | ||||
|         if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC && | ||||
|            store.header.version == DOLPHIN_STORE_HEADER_VERSION) { | ||||
|             FURI_LOG_I( | ||||
| @ -142,7 +151,7 @@ bool dolphin_state_load(DolphinState* dolphin_state) { | ||||
|         } else { | ||||
|             FURI_LOG_E( | ||||
|                 "dolphin-state", | ||||
|                 "Magic(%d != %d) and Version(%d != %d) mismatch", | ||||
|                 "Magic(%d != %d) or Version(%d != %d) mismatch", | ||||
|                 store.header.magic, | ||||
|                 DOLPHIN_STORE_HEADER_MAGIC, | ||||
|                 store.header.version, | ||||
| @ -153,6 +162,9 @@ bool dolphin_state_load(DolphinState* dolphin_state) { | ||||
| 
 | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
| 
 | ||||
|     dolphin_state->dirty = !load_result; | ||||
| 
 | ||||
|     return load_result; | ||||
| } | ||||
| 
 | ||||
| @ -160,13 +172,43 @@ void dolphin_state_clear(DolphinState* dolphin_state) { | ||||
|     memset(&dolphin_state->data, 0, sizeof(DolphinStoreData)); | ||||
| } | ||||
| 
 | ||||
| uint64_t dolphin_state_timestamp() { | ||||
|     RTC_TimeTypeDef time; | ||||
|     RTC_DateTypeDef date; | ||||
|     struct tm current; | ||||
| 
 | ||||
|     HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN); | ||||
|     HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN); | ||||
| 
 | ||||
|     current.tm_year = date.Year + 100; | ||||
|     current.tm_mday = date.Date; | ||||
|     current.tm_mon = date.Month - 1; | ||||
| 
 | ||||
|     current.tm_hour = time.Hours; | ||||
|     current.tm_min = time.Minutes; | ||||
|     current.tm_sec = time.Seconds; | ||||
| 
 | ||||
|     return mktime(¤t); | ||||
| } | ||||
| 
 | ||||
| void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | ||||
|     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed); | ||||
|     int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter; | ||||
|     int32_t butthurt = dolphin_state->data.butthurt; | ||||
| 
 | ||||
|     if(icounter >= 0) { | ||||
|         dolphin_state->data.icounter = icounter; | ||||
|         dolphin_state->data.butthurt = MAX(butthurt - deed_weight->icounter, 0); | ||||
|         dolphin_state->data.timestamp = dolphin_state_timestamp(); | ||||
|     } | ||||
| 
 | ||||
|     dolphin_state->dirty = true; | ||||
| } | ||||
| 
 | ||||
| void dolphin_state_butthurted(DolphinState* dolphin_state) { | ||||
|     dolphin_state->data.butthurt++; | ||||
|     dolphin_state->data.timestamp = dolphin_state_timestamp(); | ||||
|     dolphin_state->dirty = true; | ||||
| } | ||||
| 
 | ||||
| uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state) { | ||||
| @ -177,13 +219,14 @@ uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state) { | ||||
|     return dolphin_state->data.butthurt; | ||||
| } | ||||
| 
 | ||||
| uint32_t dolphin_state_get_level(DolphinState* dolphin_state) { | ||||
|     return 0.5f + | ||||
|            sqrtf(1.0f + 8.0f * ((float)dolphin_state->data.icounter / DOLPHIN_LVL_THRESHOLD)) / | ||||
|                2.0f; | ||||
| uint64_t dolphin_state_get_timestamp(DolphinState* dolphin_state) { | ||||
|     return dolphin_state->data.timestamp; | ||||
| } | ||||
| 
 | ||||
| uint32_t dolphin_state_xp_to_levelup(DolphinState* dolphin_state, uint32_t level, bool remaining) { | ||||
|     return (DOLPHIN_LVL_THRESHOLD * level * (level + 1) / 2) - | ||||
|            (remaining ? dolphin_state->data.icounter : 0); | ||||
| uint32_t dolphin_state_get_level(uint32_t icounter) { | ||||
|     return 0.5f + sqrtf(1.0f + 8.0f * ((float)icounter / DOLPHIN_LVL_THRESHOLD)) / 2.0f; | ||||
| } | ||||
| 
 | ||||
| uint32_t dolphin_state_xp_to_levelup(uint32_t icounter, uint32_t level, bool remaining) { | ||||
|     return (DOLPHIN_LVL_THRESHOLD * level * (level + 1) / 2) - (remaining ? icounter : 0); | ||||
| } | ||||
| @ -3,6 +3,8 @@ | ||||
| #include "dolphin_deed.h" | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <rtc.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| typedef struct DolphinState DolphinState; | ||||
| 
 | ||||
| @ -16,12 +18,18 @@ bool dolphin_state_load(DolphinState* dolphin_state); | ||||
| 
 | ||||
| void dolphin_state_clear(DolphinState* dolphin_state); | ||||
| 
 | ||||
| uint64_t dolphin_state_timestamp(); | ||||
| 
 | ||||
| void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed); | ||||
| 
 | ||||
| void dolphin_state_butthurted(DolphinState* dolphin_state); | ||||
| 
 | ||||
| uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state); | ||||
| 
 | ||||
| uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state); | ||||
| 
 | ||||
| uint32_t dolphin_state_get_level(DolphinState* dolphin_state); | ||||
| uint64_t dolphin_state_get_timestamp(DolphinState* dolphin_state); | ||||
| 
 | ||||
| uint32_t dolphin_state_xp_to_levelup(DolphinState* dolphin_state, uint32_t level, bool remaining); | ||||
| uint32_t dolphin_state_get_level(uint32_t icounter); | ||||
| 
 | ||||
| uint32_t dolphin_state_xp_to_levelup(uint32_t icounter, uint32_t level, bool remaining); | ||||
| @ -1,148 +0,0 @@ | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* name; | ||||
|     const GpioPin* pin; | ||||
| } GpioItem; | ||||
| 
 | ||||
| static const GpioItem GPIO_PINS[] = { | ||||
|     {"1.2: PA7", &gpio_ext_pa7}, | ||||
|     {"1.3: PA6", &gpio_ext_pa6}, | ||||
|     {"1.4: PA4", &gpio_ext_pa4}, | ||||
|     {"1.5: PB3", &gpio_ext_pb3}, | ||||
|     {"1.6: PB2", &gpio_ext_pb2}, | ||||
|     {"1.7: PC3", &gpio_ext_pc3}, | ||||
|     {"2.7: PC1", &gpio_ext_pc1}, | ||||
|     {"2.8: PC0", &gpio_ext_pc0}, | ||||
|     {"*.*: ALL", NULL}, | ||||
| }; | ||||
| 
 | ||||
| static const size_t GPIO_PINS_COUNT = sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]); | ||||
| 
 | ||||
| typedef struct { | ||||
|     osMessageQueueId_t input_queue; | ||||
|     uint8_t gpio_index; | ||||
|     ViewPort* view_port; | ||||
|     Gui* gui; | ||||
|     NotificationApp* notification; | ||||
| } GpioTest; | ||||
| 
 | ||||
| static void gpio_test_render_callback(Canvas* canvas, void* ctx) { | ||||
|     GpioTest* gpio_test = ctx; | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 2, 10, "GPIO Control"); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     canvas_draw_str(canvas, 2, 25, GPIO_PINS[gpio_test->gpio_index].name); | ||||
| } | ||||
| 
 | ||||
| static void gpio_test_input_callback(InputEvent* input_event, void* ctx) { | ||||
|     GpioTest* gpio_test = ctx; | ||||
| 
 | ||||
|     osMessageQueuePut(gpio_test->input_queue, input_event, 0, 0); | ||||
| } | ||||
| 
 | ||||
| static void gpio_test_configure_pins(GpioMode mode) { | ||||
|     for(size_t i = 0; i < GPIO_PINS_COUNT; i++) { | ||||
|         if(!GPIO_PINS[i].pin) continue; | ||||
|         hal_gpio_write(GPIO_PINS[i].pin, false); | ||||
|         hal_gpio_init(GPIO_PINS[i].pin, mode, GpioPullNo, GpioSpeedVeryHigh); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void gpio_test_set_pin(uint8_t index, bool level) { | ||||
|     if(GPIO_PINS[index].pin) { | ||||
|         hal_gpio_write(GPIO_PINS[index].pin, level); | ||||
|     } else { | ||||
|         for(size_t i = 0; i < GPIO_PINS_COUNT; i++) { | ||||
|             if(!GPIO_PINS[i].pin) continue; | ||||
|             hal_gpio_write(GPIO_PINS[i].pin, level); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| GpioTest* gpio_test_alloc() { | ||||
|     GpioTest* instance = furi_alloc(sizeof(GpioTest)); | ||||
| 
 | ||||
|     gpio_test_configure_pins(GpioModeOutputPushPull); | ||||
| 
 | ||||
|     instance->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); | ||||
|     furi_check(instance->input_queue); | ||||
| 
 | ||||
|     instance->view_port = view_port_alloc(); | ||||
|     view_port_draw_callback_set(instance->view_port, gpio_test_render_callback, instance); | ||||
|     view_port_input_callback_set(instance->view_port, gpio_test_input_callback, instance); | ||||
| 
 | ||||
|     instance->gui = furi_record_open("gui"); | ||||
|     gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); | ||||
| 
 | ||||
|     instance->notification = furi_record_open("notification"); | ||||
| 
 | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void gpio_test_free(GpioTest* instance) { | ||||
|     furi_assert(instance); | ||||
| 
 | ||||
|     furi_record_close("notification"); | ||||
| 
 | ||||
|     view_port_enabled_set(instance->view_port, false); | ||||
|     gui_remove_view_port(instance->gui, instance->view_port); | ||||
|     furi_record_close("gui"); | ||||
| 
 | ||||
|     view_port_free(instance->view_port); | ||||
| 
 | ||||
|     osMessageQueueDelete(instance->input_queue); | ||||
| 
 | ||||
|     gpio_test_configure_pins(GpioModeAnalog); | ||||
| 
 | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| int32_t gpio_test_app(void* p) { | ||||
|     GpioTest* gpio_test = gpio_test_alloc(); | ||||
| 
 | ||||
|     InputEvent event; | ||||
|     while(osMessageQueueGet(gpio_test->input_queue, &event, NULL, osWaitForever) == osOK) { | ||||
|         if(event.type == InputTypeShort) { | ||||
|             if(event.key == InputKeyBack) { | ||||
|                 notification_message(gpio_test->notification, &sequence_reset_green); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(event.key == InputKeyRight) { | ||||
|                 if(gpio_test->gpio_index < (GPIO_PINS_COUNT - 1)) { | ||||
|                     gpio_test->gpio_index++; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(event.key == InputKeyLeft) { | ||||
|                 if(gpio_test->gpio_index > 0) { | ||||
|                     gpio_test->gpio_index--; | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             if(event.key == InputKeyOk) { | ||||
|                 if(event.type == InputTypePress) { | ||||
|                     gpio_test_set_pin(gpio_test->gpio_index, true); | ||||
|                     notification_message(gpio_test->notification, &sequence_set_green_255); | ||||
|                 } else if(event.type == InputTypeRelease) { | ||||
|                     gpio_test_set_pin(gpio_test->gpio_index, false); | ||||
|                     notification_message(gpio_test->notification, &sequence_reset_green); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         view_port_update(gpio_test->view_port); | ||||
|     } | ||||
| 
 | ||||
|     gpio_test_free(gpio_test); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										76
									
								
								applications/gpio/gpio_app.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										76
									
								
								applications/gpio/gpio_app.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,76 @@ | ||||
| #include "gpio_app_i.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| static bool gpio_app_custom_event_callback(void* context, uint32_t event) { | ||||
|     furi_assert(context); | ||||
|     GpioApp* app = context; | ||||
|     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||
| } | ||||
| 
 | ||||
| static bool gpio_app_back_event_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     GpioApp* app = context; | ||||
|     return scene_manager_handle_back_event(app->scene_manager); | ||||
| } | ||||
| 
 | ||||
| GpioApp* gpio_app_alloc() { | ||||
|     GpioApp* app = furi_alloc(sizeof(GpioApp)); | ||||
| 
 | ||||
|     app->gui = furi_record_open("gui"); | ||||
|     app->notifications = furi_record_open("notification"); | ||||
| 
 | ||||
|     app->view_dispatcher = view_dispatcher_alloc(); | ||||
|     app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); | ||||
|     view_dispatcher_enable_queue(app->view_dispatcher); | ||||
|     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||
| 
 | ||||
|     view_dispatcher_set_custom_event_callback( | ||||
|         app->view_dispatcher, gpio_app_custom_event_callback); | ||||
|     view_dispatcher_set_navigation_event_callback( | ||||
|         app->view_dispatcher, gpio_app_back_event_callback); | ||||
| 
 | ||||
|     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||
| 
 | ||||
|     app->var_item_list = variable_item_list_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, | ||||
|         GpioAppViewVarItemList, | ||||
|         variable_item_list_get_view(app->var_item_list)); | ||||
|     app->gpio_test = gpio_test_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, GpioAppViewGpioTest, gpio_test_get_view(app->gpio_test)); | ||||
| 
 | ||||
|     scene_manager_next_scene(app->scene_manager, GpioSceneStart); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| void gpio_app_free(GpioApp* app) { | ||||
|     furi_assert(app); | ||||
| 
 | ||||
|     // Views
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewVarItemList); | ||||
|     variable_item_list_free(app->var_item_list); | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewGpioTest); | ||||
|     gpio_test_free(app->gpio_test); | ||||
|     // View dispatcher
 | ||||
|     view_dispatcher_free(app->view_dispatcher); | ||||
|     scene_manager_free(app->scene_manager); | ||||
|     // Close records
 | ||||
|     furi_record_close("gui"); | ||||
|     furi_record_close("notification"); | ||||
| 
 | ||||
|     free(app); | ||||
| } | ||||
| 
 | ||||
| int32_t gpio_app(void* p) { | ||||
|     GpioApp* gpio_app = gpio_app_alloc(); | ||||
| 
 | ||||
|     view_dispatcher_run(gpio_app->view_dispatcher); | ||||
| 
 | ||||
|     gpio_app_free(gpio_app); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -4,7 +4,7 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void MX_USB_Device_Init(); | ||||
| typedef struct GpioApp GpioApp; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
							
								
								
									
										28
									
								
								applications/gpio/gpio_app_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								applications/gpio/gpio_app_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "gpio_app.h" | ||||
| #include "gpio_item.h" | ||||
| #include "scenes/gpio_scene.h" | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| #include <gui/modules/variable-item-list.h> | ||||
| #include "views/gpio_test.h" | ||||
| 
 | ||||
| struct GpioApp { | ||||
|     Gui* gui; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     SceneManager* scene_manager; | ||||
|     NotificationApp* notifications; | ||||
| 
 | ||||
|     VariableItemList* var_item_list; | ||||
|     GpioTest* gpio_test; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     GpioAppViewVarItemList, | ||||
|     GpioAppViewGpioTest, | ||||
| } GpioAppView; | ||||
							
								
								
									
										51
									
								
								applications/gpio/gpio_item.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								applications/gpio/gpio_item.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| #include "gpio_item.h" | ||||
| 
 | ||||
| #include <furi-hal-resources.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* name; | ||||
|     const GpioPin* pin; | ||||
| } GpioItem; | ||||
| 
 | ||||
| static const GpioItem gpio_item[GPIO_ITEM_COUNT] = { | ||||
|     {"1.2: PA7", &gpio_ext_pa7}, | ||||
|     {"1.3: PA6", &gpio_ext_pa6}, | ||||
|     {"1.4: PA4", &gpio_ext_pa4}, | ||||
|     {"1.5: PB3", &gpio_ext_pb3}, | ||||
|     {"1.6: PB2", &gpio_ext_pb2}, | ||||
|     {"1.7: PC3", &gpio_ext_pc3}, | ||||
|     {"2.7: PC1", &gpio_ext_pc1}, | ||||
|     {"2.8: PC0", &gpio_ext_pc0}, | ||||
| }; | ||||
| 
 | ||||
| void gpio_item_configure_pin(uint8_t index, GpioMode mode) { | ||||
|     furi_assert(index < GPIO_ITEM_COUNT); | ||||
|     hal_gpio_write(gpio_item[index].pin, false); | ||||
|     hal_gpio_init(gpio_item[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh); | ||||
| } | ||||
| 
 | ||||
| void gpio_item_configure_all_pins(GpioMode mode) { | ||||
|     for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { | ||||
|         gpio_item_configure_pin(i, mode); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void gpio_item_set_pin(uint8_t index, bool level) { | ||||
|     furi_assert(index < GPIO_ITEM_COUNT); | ||||
|     hal_gpio_write(gpio_item[index].pin, level); | ||||
| } | ||||
| 
 | ||||
| void gpio_item_set_all_pins(bool level) { | ||||
|     for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { | ||||
|         gpio_item_set_pin(i, level); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* gpio_item_get_pin_name(uint8_t index) { | ||||
|     furi_assert(index < GPIO_ITEM_COUNT + 1); | ||||
|     if(index == GPIO_ITEM_COUNT) { | ||||
|         return "ALL"; | ||||
|     } else { | ||||
|         return gpio_item[index].name; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								applications/gpio/gpio_item.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								applications/gpio/gpio_item.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi-hal-gpio.h> | ||||
| 
 | ||||
| #define GPIO_ITEM_COUNT 8 | ||||
| 
 | ||||
| void gpio_item_configure_pin(uint8_t index, GpioMode mode); | ||||
| 
 | ||||
| void gpio_item_configure_all_pins(GpioMode mode); | ||||
| 
 | ||||
| void gpio_item_set_pin(uint8_t index, bool level); | ||||
| 
 | ||||
| void gpio_item_set_all_pins(bool level); | ||||
| 
 | ||||
| const char* gpio_item_get_pin_name(uint8_t index); | ||||
							
								
								
									
										30
									
								
								applications/gpio/scenes/gpio_scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/gpio/scenes/gpio_scene.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| #include "gpio_scene.h" | ||||
| 
 | ||||
| // Generate scene on_enter handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||
| void (*const gpio_scene_on_enter_handlers[])(void*) = { | ||||
| #include "gpio_scene_config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_event handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||
| bool (*const gpio_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||
| #include "gpio_scene_config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_exit handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||
| void (*const gpio_scene_on_exit_handlers[])(void* context) = { | ||||
| #include "gpio_scene_config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Initialize scene handlers configuration structure
 | ||||
| const SceneManagerHandlers gpio_scene_handlers = { | ||||
|     .on_enter_handlers = gpio_scene_on_enter_handlers, | ||||
|     .on_event_handlers = gpio_scene_on_event_handlers, | ||||
|     .on_exit_handlers = gpio_scene_on_exit_handlers, | ||||
|     .scene_num = GpioSceneNum, | ||||
| }; | ||||
							
								
								
									
										29
									
								
								applications/gpio/scenes/gpio_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/gpio/scenes/gpio_scene.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| // Generate scene id and total number
 | ||||
| #define ADD_SCENE(prefix, name, id) GpioScene##id, | ||||
| typedef enum { | ||||
| #include "gpio_scene_config.h" | ||||
|     GpioSceneNum, | ||||
| } GpioScene; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| extern const SceneManagerHandlers gpio_scene_handlers; | ||||
| 
 | ||||
| // Generate scene on_enter handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||
| #include "gpio_scene_config.h" | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_event handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) \ | ||||
|     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||
| #include "gpio_scene_config.h" | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_exit handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||
| #include "gpio_scene_config.h" | ||||
| #undef ADD_SCENE | ||||
							
								
								
									
										2
									
								
								applications/gpio/scenes/gpio_scene_config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								applications/gpio/scenes/gpio_scene_config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| ADD_SCENE(gpio, start, Start) | ||||
| ADD_SCENE(gpio, test, Test) | ||||
							
								
								
									
										92
									
								
								applications/gpio/scenes/gpio_scene_start.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										92
									
								
								applications/gpio/scenes/gpio_scene_start.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,92 @@ | ||||
| #include "../gpio_app_i.h" | ||||
| #include "furi-hal-power.h" | ||||
| 
 | ||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF (0UL) | ||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON (1UL) | ||||
| #define GPIO_SCENE_START_CUSTOM_EVENT_TEST (2UL) | ||||
| 
 | ||||
| enum GpioItem { | ||||
|     GpioItemOtg, | ||||
|     GpioItemTest, | ||||
| }; | ||||
| 
 | ||||
| enum GpioOtg { | ||||
|     GpioOtgOff, | ||||
|     GpioOtgOn, | ||||
|     GpioOtgSettingsNum, | ||||
| }; | ||||
| 
 | ||||
| const char* const gpio_otg_text[GpioOtgSettingsNum] = { | ||||
|     "Off", | ||||
|     "On", | ||||
| }; | ||||
| 
 | ||||
| static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t index) { | ||||
|     furi_assert(context); | ||||
|     GpioApp* app = context; | ||||
|     if(index == GpioItemTest) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             app->view_dispatcher, GPIO_SCENE_START_CUSTOM_EVENT_TEST); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void gpio_scene_start_var_list_change_callback(VariableItem* item) { | ||||
|     GpioApp* app = variable_item_get_context(item); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, gpio_otg_text[index]); | ||||
|     if(index == GpioOtgOff) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             app->view_dispatcher, GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF); | ||||
|     } else if(index == GpioOtgOn) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             app->view_dispatcher, GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_start_on_enter(void* context) { | ||||
|     GpioApp* app = context; | ||||
|     VariableItemList* var_item_list = app->var_item_list; | ||||
| 
 | ||||
|     VariableItem* item; | ||||
|     variable_item_list_set_enter_callback( | ||||
|         var_item_list, gpio_scene_start_var_list_enter_callback, app); | ||||
|     item = variable_item_list_add( | ||||
|         var_item_list, | ||||
|         "5V on GPIO", | ||||
|         GpioOtgSettingsNum, | ||||
|         gpio_scene_start_var_list_change_callback, | ||||
|         app); | ||||
|     if(furi_hal_power_is_otg_enabled()) { | ||||
|         variable_item_set_current_value_index(item, GpioOtgOn); | ||||
|         variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]); | ||||
|     } else { | ||||
|         variable_item_set_current_value_index(item, GpioOtgOff); | ||||
|         variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOff]); | ||||
|     } | ||||
|     variable_item_list_add(var_item_list, "GPIO tester", 0, NULL, NULL); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); | ||||
| } | ||||
| 
 | ||||
| bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
|     GpioApp* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_ON) { | ||||
|             furi_hal_power_enable_otg(); | ||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF) { | ||||
|             furi_hal_power_disable_otg(); | ||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_TEST) { | ||||
|             scene_manager_next_scene(app->scene_manager, GpioSceneTest); | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_start_on_exit(void* context) { | ||||
|     GpioApp* app = context; | ||||
|     variable_item_list_clean(app->var_item_list); | ||||
| } | ||||
							
								
								
									
										27
									
								
								applications/gpio/scenes/gpio_scene_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								applications/gpio/scenes/gpio_scene_test.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| #include "../gpio_app_i.h" | ||||
| 
 | ||||
| void gpio_scene_test_ok_callback(InputType type, void* context) { | ||||
|     furi_assert(context); | ||||
|     GpioApp* app = context; | ||||
| 
 | ||||
|     if(type == InputTypePress) { | ||||
|         notification_message(app->notifications, &sequence_set_green_255); | ||||
|     } else if(type == InputTypeRelease) { | ||||
|         notification_message(app->notifications, &sequence_reset_green); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_test_on_enter(void* context) { | ||||
|     GpioApp* app = context; | ||||
|     gpio_item_configure_all_pins(GpioModeOutputPushPull); | ||||
|     gpio_test_set_ok_callback(app->gpio_test, gpio_scene_test_ok_callback, app); | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioTest); | ||||
| } | ||||
| 
 | ||||
| bool gpio_scene_test_on_event(void* context, SceneManagerEvent event) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_test_on_exit(void* context) { | ||||
|     gpio_item_configure_all_pins(GpioModeAnalog); | ||||
| } | ||||
							
								
								
									
										130
									
								
								applications/gpio/views/gpio_test.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										130
									
								
								applications/gpio/views/gpio_test.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,130 @@ | ||||
| #include "gpio_test.h" | ||||
| #include "../gpio_item.h" | ||||
| 
 | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| struct GpioTest { | ||||
|     View* view; | ||||
|     GpioTestOkCallback callback; | ||||
|     void* context; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t pin_idx; | ||||
| } GpioTestModel; | ||||
| 
 | ||||
| static bool gpio_test_process_left(GpioTest* gpio_test); | ||||
| static bool gpio_test_process_right(GpioTest* gpio_test); | ||||
| static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event); | ||||
| 
 | ||||
| static void gpio_test_draw_callback(Canvas* canvas, void* _model) { | ||||
|     GpioTestModel* model = _model; | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Gpio Output mode test"); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     elements_multiline_text_aligned( | ||||
|         canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin"); | ||||
|     elements_multiline_text_aligned( | ||||
|         canvas, 64, 32, AlignCenter, AlignTop, gpio_item_get_pin_name(model->pin_idx)); | ||||
| } | ||||
| 
 | ||||
| static bool gpio_test_input_callback(InputEvent* event, void* context) { | ||||
|     furi_assert(context); | ||||
|     GpioTest* gpio_test = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if(event->key == InputKeyRight) { | ||||
|             consumed = gpio_test_process_right(gpio_test); | ||||
|         } else if(event->key == InputKeyLeft) { | ||||
|             consumed = gpio_test_process_left(gpio_test); | ||||
|         } | ||||
|     } else if(event->key == InputKeyOk) { | ||||
|         consumed = gpio_test_process_ok(gpio_test, event); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| static bool gpio_test_process_left(GpioTest* gpio_test) { | ||||
|     with_view_model( | ||||
|         gpio_test->view, (GpioTestModel * model) { | ||||
|             if(model->pin_idx) { | ||||
|                 model->pin_idx--; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool gpio_test_process_right(GpioTest* gpio_test) { | ||||
|     with_view_model( | ||||
|         gpio_test->view, (GpioTestModel * model) { | ||||
|             if(model->pin_idx < GPIO_ITEM_COUNT) { | ||||
|                 model->pin_idx++; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         gpio_test->view, (GpioTestModel * model) { | ||||
|             if(event->type == InputTypePress) { | ||||
|                 if(model->pin_idx < GPIO_ITEM_COUNT) { | ||||
|                     gpio_item_set_pin(model->pin_idx, true); | ||||
|                 } else { | ||||
|                     gpio_item_set_all_pins(true); | ||||
|                 } | ||||
|                 consumed = true; | ||||
|             } else if(event->type == InputTypeRelease) { | ||||
|                 if(model->pin_idx < GPIO_ITEM_COUNT) { | ||||
|                     gpio_item_set_pin(model->pin_idx, false); | ||||
|                 } else { | ||||
|                     gpio_item_set_all_pins(false); | ||||
|                 } | ||||
|                 consumed = true; | ||||
|             } | ||||
|             gpio_test->callback(event->type, gpio_test->context); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| GpioTest* gpio_test_alloc() { | ||||
|     GpioTest* gpio_test = furi_alloc(sizeof(GpioTest)); | ||||
| 
 | ||||
|     gpio_test->view = view_alloc(); | ||||
|     view_allocate_model(gpio_test->view, ViewModelTypeLocking, sizeof(GpioTestModel)); | ||||
|     view_set_context(gpio_test->view, gpio_test); | ||||
|     view_set_draw_callback(gpio_test->view, gpio_test_draw_callback); | ||||
|     view_set_input_callback(gpio_test->view, gpio_test_input_callback); | ||||
| 
 | ||||
|     return gpio_test; | ||||
| } | ||||
| 
 | ||||
| void gpio_test_free(GpioTest* gpio_test) { | ||||
|     furi_assert(gpio_test); | ||||
|     view_free(gpio_test->view); | ||||
|     free(gpio_test); | ||||
| } | ||||
| 
 | ||||
| View* gpio_test_get_view(GpioTest* gpio_test) { | ||||
|     furi_assert(gpio_test); | ||||
|     return gpio_test->view; | ||||
| } | ||||
| 
 | ||||
| void gpio_test_set_ok_callback(GpioTest* gpio_test, GpioTestOkCallback callback, void* context) { | ||||
|     furi_assert(gpio_test); | ||||
|     furi_assert(callback); | ||||
|     with_view_model( | ||||
|         gpio_test->view, (GpioTestModel * model) { | ||||
|             gpio_test->callback = callback; | ||||
|             gpio_test->context = context; | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
							
								
								
									
										14
									
								
								applications/gpio/views/gpio_test.h
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										14
									
								
								applications/gpio/views/gpio_test.h
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| typedef struct GpioTest GpioTest; | ||||
| typedef void (*GpioTestOkCallback)(InputType type, void* context); | ||||
| 
 | ||||
| GpioTest* gpio_test_alloc(); | ||||
| 
 | ||||
| void gpio_test_free(GpioTest* gpio_test); | ||||
| 
 | ||||
| View* gpio_test_get_view(GpioTest* gpio_test); | ||||
| 
 | ||||
| void gpio_test_set_ok_callback(GpioTest* gpio_test, GpioTestOkCallback callback, void* context); | ||||
| @ -110,11 +110,13 @@ void canvas_set_font(Canvas* canvas, Font font) { | ||||
|     furi_assert(canvas); | ||||
|     u8g2_SetFontMode(&canvas->fb, 1); | ||||
|     if(font == FontPrimary) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_helvB08_tf); | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_helvB08_tr); | ||||
|     } else if(font == FontSecondary) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr); | ||||
|     } else if(font == FontKeyboard) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mf); | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mr); | ||||
|     } else if(font == FontBigNumbers) { | ||||
|         u8g2_SetFont(&canvas->fb, u8g2_font_profont22_tn); | ||||
|     } else { | ||||
|         furi_crash(NULL); | ||||
|     } | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file canvas.h | ||||
|  * GUI: Canvas API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| @ -8,13 +13,16 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /** Color enumeration */ | ||||
| typedef enum { | ||||
|     ColorWhite = 0x00, | ||||
|     ColorBlack = 0x01, | ||||
| } Color; | ||||
| 
 | ||||
| typedef enum { FontPrimary, FontSecondary, FontKeyboard } Font; | ||||
| /** Fonts enumeration */ | ||||
| typedef enum { FontPrimary, FontSecondary, FontKeyboard, FontBigNumbers } Font; | ||||
| 
 | ||||
| /** Alignment enumeration */ | ||||
| typedef enum { | ||||
|     AlignLeft, | ||||
|     AlignRight, | ||||
| @ -23,59 +31,85 @@ typedef enum { | ||||
|     AlignCenter, | ||||
| } Align; | ||||
| 
 | ||||
| /** Canvas Orientation */ | ||||
| typedef enum { | ||||
|     CanvasOrientationHorizontal, | ||||
|     CanvasOrientationVertical, | ||||
| } CanvasOrientation; | ||||
| 
 | ||||
| /** Canvas anonymouse structure */ | ||||
| typedef struct Canvas Canvas; | ||||
| 
 | ||||
| /*
 | ||||
|  * Canvas width | ||||
| /** Get Canvas width
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * | ||||
|  * @return     width in pixels. | ||||
|  */ | ||||
| uint8_t canvas_width(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Canvas height | ||||
| /** Get Canvas height
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * | ||||
|  * @return     height in pixels. | ||||
|  */ | ||||
| uint8_t canvas_height(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get current font height | ||||
| /** Get current font height
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * | ||||
|  * @return     height in pixels. | ||||
|  */ | ||||
| uint8_t canvas_current_font_height(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Clear canvas, clear rendering buffer | ||||
| /** Clear canvas
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  */ | ||||
| void canvas_clear(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Set drawing color | ||||
| /** Set drawing color
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      color   Color | ||||
|  */ | ||||
| void canvas_set_color(Canvas* canvas, Color color); | ||||
| 
 | ||||
| /*
 | ||||
|  * Invert drawing color | ||||
| /** Invert drawing color
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  */ | ||||
| void canvas_invert_color(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Set drawing font | ||||
| /** Set drawing font
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      font    Font | ||||
|  */ | ||||
| void canvas_set_font(Canvas* canvas, Font font); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw string at position of baseline defined by x, y. | ||||
| /** Draw string at position of baseline defined by x, y.
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       anchor point x coordinate | ||||
|  * @param      y       anchor point y coordinate | ||||
|  * @param      str     C-string | ||||
|  */ | ||||
| void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw aligned string defined by x, y. | ||||
|  * Align calculated from position of baseline, string width and ascent (height of the glyphs above the baseline) | ||||
| /** Draw aligned string defined by x, y.
 | ||||
|  * | ||||
|  * Align calculated from position of baseline, string width and ascent (height | ||||
|  * of the glyphs above the baseline) | ||||
|  * | ||||
|  * @param      canvas      Canvas instance | ||||
|  * @param      x           anchor point x coordinate | ||||
|  * @param      y           anchor point y coordinate | ||||
|  * @param      horizontal  horizontal alignment | ||||
|  * @param      vertical    vertical alignment | ||||
|  * @param      str         C-string | ||||
|  */ | ||||
| void canvas_draw_str_aligned( | ||||
|     Canvas* canvas, | ||||
| @ -85,22 +119,30 @@ void canvas_draw_str_aligned( | ||||
|     Align vertical, | ||||
|     const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get string width | ||||
| /** Get string width
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      str     C-string | ||||
|  * | ||||
|  * @return     width in pixels. | ||||
|  */ | ||||
| uint16_t canvas_string_width(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /** Get glyph width
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param[in]  symbol  character | ||||
|  * | ||||
|  * @return     width in pixels | ||||
|  */ | ||||
| uint8_t canvas_glyph_width(Canvas* canvas, char symbol); | ||||
| 
 | ||||
| /** Draw animation at position defined by x,y.
 | ||||
|  * @param canvas - canvas instance | ||||
|  * @param x - x coordinate | ||||
|  * @param y - y coordinate | ||||
|  * @param icon_animation - data pointer to IconAnimation | ||||
|  * | ||||
|  * @param      canvas          Canvas instance | ||||
|  * @param      x               x coordinate | ||||
|  * @param      y               y coordinate | ||||
|  * @param      icon_animation  IconAnimation instance | ||||
|  */ | ||||
| void canvas_draw_icon_animation( | ||||
|     Canvas* canvas, | ||||
| @ -109,15 +151,22 @@ void canvas_draw_icon_animation( | ||||
|     IconAnimation* icon_animation); | ||||
| 
 | ||||
| /** Draw icon at position defined by x,y.
 | ||||
|  * @param canvas - canvas instance | ||||
|  * @param x - x coordinate | ||||
|  * @param y - y coordinate | ||||
|  * @param icon - data pointer to Icon | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      icon    Icon instance | ||||
|  */ | ||||
| void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw xbm icon of width, height at position defined by x,y. | ||||
| /** Draw XBM bitmap
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      w       bitmap width | ||||
|  * @param      h       bitmap height | ||||
|  * @param      bitmap  pointer to XBM bitmap data | ||||
|  */ | ||||
| void canvas_draw_xbm( | ||||
|     Canvas* canvas, | ||||
| @ -127,48 +176,86 @@ void canvas_draw_xbm( | ||||
|     uint8_t h, | ||||
|     const uint8_t* bitmap); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw dot at x,y | ||||
| /** Draw dot at x,y
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  */ | ||||
| void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw box of width, height at x,y | ||||
| /** Draw box of width, height at x,y
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      width   box width | ||||
|  * @param      height  box height | ||||
|  */ | ||||
| void canvas_draw_box(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw frame of width, height at x,y | ||||
| /** Draw frame of width, height at x,y
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      width   frame width | ||||
|  * @param      height  frame height | ||||
|  */ | ||||
| void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw line from x1,y1 to x2,y2 | ||||
| /** Draw line from x1,y1 to x2,y2
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x1      x1 coordinate | ||||
|  * @param      y1      y1 coordinate | ||||
|  * @param      x2      x2 coordinate | ||||
|  * @param      y2      y2 coordinate | ||||
|  */ | ||||
| void canvas_draw_line(Canvas* canvas, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw circle at x,y with radius r | ||||
| /** Draw circle at x,y with radius r
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      r       radius | ||||
|  */ | ||||
| void canvas_draw_circle(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw disc at x,y with radius r | ||||
| /** Draw disc at x,y with radius r
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      r       radius | ||||
|  */ | ||||
| void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw glyph | ||||
| /** Draw glyph
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      ch      character | ||||
|  */ | ||||
| void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch); | ||||
| 
 | ||||
| /*
 | ||||
|  * Set transparency mode | ||||
| /** Set transparency mode
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      alpha   transparency mode | ||||
|  */ | ||||
| void canvas_set_bitmap_mode(Canvas* canvas, bool alpha); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw rounded-corner frame of width, height at x,y, with round value raduis | ||||
| /** Draw rounded-corner frame of width, height at x,y, with round value raduis
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      width   frame width | ||||
|  * @param      height  frame height | ||||
|  * @param      radius  frame corner radius | ||||
|  */ | ||||
| void canvas_draw_rframe( | ||||
|     Canvas* canvas, | ||||
| @ -178,8 +265,14 @@ void canvas_draw_rframe( | ||||
|     uint8_t height, | ||||
|     uint8_t radius); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw rounded-corner box of width, height at x,y, with round value raduis | ||||
| /** Draw rounded-corner box of width, height at x,y, with round value raduis
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * @param      x       x coordinate | ||||
|  * @param      y       y coordinate | ||||
|  * @param      width   box width | ||||
|  * @param      height  box height | ||||
|  * @param      radius  box corner radius | ||||
|  */ | ||||
| void canvas_draw_rbox( | ||||
|     Canvas* canvas, | ||||
|  | ||||
| @ -1,8 +1,15 @@ | ||||
| /**
 | ||||
|  * @file canvas_i.h | ||||
|  * GUI: internal Canvas API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "canvas.h" | ||||
| #include <u8g2.h> | ||||
| 
 | ||||
| /** Canvas structure
 | ||||
|  */ | ||||
| struct Canvas { | ||||
|     u8g2_t fb; | ||||
|     CanvasOrientation orientation; | ||||
| @ -12,40 +19,53 @@ struct Canvas { | ||||
|     uint8_t height; | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * Allocate memory and initialize canvas | ||||
| /** Allocate memory and initialize canvas
 | ||||
|  * | ||||
|  * @return     Canvas instance | ||||
|  */ | ||||
| Canvas* canvas_init(); | ||||
| 
 | ||||
| /*
 | ||||
|  * Free canvas memory | ||||
| /** Free canvas memory
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  */ | ||||
| void canvas_free(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Reset canvas drawing tools configuration | ||||
| /** Reset canvas drawing tools configuration
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  */ | ||||
| void canvas_reset(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Commit canvas. Send buffer to display | ||||
| /** Commit canvas. Send buffer to display
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  */ | ||||
| void canvas_commit(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get canvas buffer. | ||||
| /** Get canvas buffer.
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * | ||||
|  * @return     pointer to buffer | ||||
|  */ | ||||
| uint8_t* canvas_get_buffer(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get canvas buffer size. | ||||
| /** Get canvas buffer size.
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * | ||||
|  * @return     size of canvas in bytes | ||||
|  */ | ||||
| size_t canvas_get_buffer_size(Canvas* canvas); | ||||
| 
 | ||||
| /*
 | ||||
|  * Set drawing region relative to real screen buffer | ||||
| /** Set drawing region relative to real screen buffer
 | ||||
|  * | ||||
|  * @param      canvas    Canvas instance | ||||
|  * @param      offset_x  x coordinate offset | ||||
|  * @param      offset_y  y coordinate offset | ||||
|  * @param      width     width | ||||
|  * @param      height    height | ||||
|  */ | ||||
| void canvas_frame_set( | ||||
|     Canvas* canvas, | ||||
| @ -54,12 +74,17 @@ void canvas_frame_set( | ||||
|     uint8_t width, | ||||
|     uint8_t height); | ||||
| 
 | ||||
| /*
 | ||||
|  * Set canvas orientation | ||||
| /** Set canvas orientation
 | ||||
|  * | ||||
|  * @param      canvas       Canvas instance | ||||
|  * @param      orientation  CanvasOrientation | ||||
|  */ | ||||
| void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get canvas orientation | ||||
| /** Get canvas orientation
 | ||||
|  * | ||||
|  * @param      canvas  Canvas instance | ||||
|  * | ||||
|  * @return     CanvasOrientation | ||||
|  */ | ||||
| CanvasOrientation canvas_get_orientation(const Canvas* canvas); | ||||
|  | ||||
| @ -278,7 +278,7 @@ void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* t | ||||
|         canvas_draw_str(canvas, x, y, string_get_cstr(str)); | ||||
|         start = end + 1; | ||||
|         y += font_height; | ||||
|     } while(end); | ||||
|     } while(end && y < 64); | ||||
|     string_clear(str); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,11 @@ | ||||
| /**
 | ||||
|  * @file elements.h | ||||
|  * GUI: Elements API | ||||
|  *  | ||||
|  * Canvas helpers and UI building blocks. | ||||
|  *  | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
|  | ||||
| @ -335,7 +335,7 @@ void gui_remove_view_port(Gui* gui, ViewPort* view_port) { | ||||
|     gui_unlock(gui); | ||||
| } | ||||
| 
 | ||||
| void gui_send_view_port_front(Gui* gui, ViewPort* view_port) { | ||||
| void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port) { | ||||
|     furi_assert(gui); | ||||
|     furi_assert(view_port); | ||||
| 
 | ||||
| @ -361,7 +361,7 @@ void gui_send_view_port_front(Gui* gui, ViewPort* view_port) { | ||||
|     gui_unlock(gui); | ||||
| } | ||||
| 
 | ||||
| void gui_send_view_port_back(Gui* gui, ViewPort* view_port) { | ||||
| void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { | ||||
|     furi_assert(gui); | ||||
|     furi_assert(view_port); | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file gui.h | ||||
|  * GUI: main API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "view_port.h" | ||||
| @ -7,60 +12,74 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Gui layers */ | ||||
| /** Gui layers */ | ||||
| typedef enum { | ||||
|     GuiLayerNone, /* Special layer for internal use only */ | ||||
|     GuiLayerNone, /**< Special layer for internal use only */ | ||||
| 
 | ||||
|     GuiLayerStatusBarLeft, /* Status bar left-side layer, auto-layout */ | ||||
|     GuiLayerStatusBarRight, /* Status bar right-side layer, auto-layout */ | ||||
|     GuiLayerMain, /* Main layer, status bar is shown */ | ||||
|     GuiLayerFullscreen, /* Fullscreen layer */ | ||||
|     GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */ | ||||
|     GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */ | ||||
|     GuiLayerMain, /**< Main layer, status bar is shown */ | ||||
|     GuiLayerFullscreen, /**< Fullscreen layer */ | ||||
| 
 | ||||
|     GuiLayerMAX /* Don't use or move, special value */ | ||||
|     GuiLayerMAX /**< Don't use or move, special value */ | ||||
| } GuiLayer; | ||||
| 
 | ||||
| /* Gui frame buffer callback */ | ||||
| /** Gui Canvas Commit Callback */ | ||||
| typedef void (*GuiCanvasCommitCallback)(uint8_t* data, size_t size, void* context); | ||||
| 
 | ||||
| typedef struct Gui Gui; | ||||
| 
 | ||||
| /*
 | ||||
|  * Add view_port to view_port tree | ||||
|  * @remarks thread safe | ||||
| /** Add view_port to view_port tree
 | ||||
|  * | ||||
|  * @remark     thread safe | ||||
|  * | ||||
|  * @param      gui        Gui instance | ||||
|  * @param      view_port  ViewPort instance | ||||
|  * @param[in]  layer      GuiLayer where to place view_port | ||||
|  */ | ||||
| void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer); | ||||
| 
 | ||||
| /*
 | ||||
|  * Remove view_port from rendering tree | ||||
|  * @remarks thread safe | ||||
| /** Remove view_port from rendering tree
 | ||||
|  * | ||||
|  * @remark     thread safe | ||||
|  * | ||||
|  * @param      gui        Gui instance | ||||
|  * @param      view_port  ViewPort instance | ||||
|  */ | ||||
| void gui_remove_view_port(Gui* gui, ViewPort* view_port); | ||||
| 
 | ||||
| /* Send ViewPort to the front
 | ||||
| /** Send ViewPort to the front
 | ||||
|  * | ||||
|  * Places selected ViewPort to the top of the drawing stack | ||||
|  * @param gui - Gui instance | ||||
|  * @param view_port - ViewPort instance | ||||
|  * | ||||
|  * @param      gui        Gui instance | ||||
|  * @param      view_port  ViewPort instance | ||||
|  */ | ||||
| void gui_send_view_port_front(Gui* gui, ViewPort* view_port); | ||||
| void gui_view_port_send_to_front(Gui* gui, ViewPort* view_port); | ||||
| 
 | ||||
| /* Send ViewPort to the back
 | ||||
| /** Send ViewPort to the back
 | ||||
|  * | ||||
|  * Places selected ViewPort to the bottom of the drawing stack | ||||
|  * @param gui - Gui instance | ||||
|  * @param view_port - ViewPort instance | ||||
|  * | ||||
|  * @param      gui        Gui instance | ||||
|  * @param      view_port  ViewPort instance | ||||
|  */ | ||||
| void gui_send_view_port_back(Gui* gui, ViewPort* view_port); | ||||
| void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); | ||||
| 
 | ||||
| /* Set gui canvas commit callback
 | ||||
|  * This callback will be called upon Canvas commit | ||||
|  * Callback dispatched from GUI thread and is time critical | ||||
|  * @param gui - Gui instance | ||||
|  * @param callback - GuiCanvasCommitCallback | ||||
| /** Set gui canvas commit callback
 | ||||
|  * | ||||
|  * This callback will be called upon Canvas commit Callback dispatched from GUI | ||||
|  * thread and is time critical | ||||
|  * | ||||
|  * @param      gui       Gui instance | ||||
|  * @param      callback  GuiCanvasCommitCallback | ||||
|  */ | ||||
| void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback); | ||||
| 
 | ||||
| /* Set gui canvas commit callback context
 | ||||
|  * @param gui - Gui instance | ||||
|  * @param context - pointer to context | ||||
| /** Set gui canvas commit callback context
 | ||||
|  * | ||||
|  * @param      gui      Gui instance | ||||
|  * @param      context  pointer to context | ||||
|  */ | ||||
| void gui_set_framebuffer_callback_context(Gui* gui, void* context); | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file gui_i.h | ||||
|  * GUI: main API internals | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "gui.h" | ||||
| @ -31,6 +36,7 @@ | ||||
| 
 | ||||
| ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); | ||||
| 
 | ||||
| /** Gui structure */ | ||||
| struct Gui { | ||||
|     // Thread and lock
 | ||||
|     osThreadId_t thread; | ||||
| @ -54,8 +60,9 @@ struct Gui { | ||||
| 
 | ||||
| ViewPort* gui_view_port_find_enabled(ViewPortArray_t array); | ||||
| 
 | ||||
| /* Update GUI, request redraw
 | ||||
|  * @param gui, Gui instance | ||||
| /** Update GUI, request redraw
 | ||||
|  * | ||||
|  * @param      gui   Gui instance | ||||
|  */ | ||||
| void gui_update(Gui* gui); | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file icon.h | ||||
|  * GUI: Icon API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| @ -8,10 +13,28 @@ extern "C" { | ||||
| 
 | ||||
| typedef struct Icon Icon; | ||||
| 
 | ||||
| /** Get icon width 
 | ||||
|  * | ||||
|  * @param[in]  instance  pointer to Icon data | ||||
|  * | ||||
|  * @return     width in pixels | ||||
|  */ | ||||
| uint8_t icon_get_width(const Icon* instance); | ||||
| 
 | ||||
| /** Get icon height
 | ||||
|  * | ||||
|  * @param[in]  instance  pointer to Icon data | ||||
|  * | ||||
|  * @return     height in pixels | ||||
|  */ | ||||
| uint8_t icon_get_height(const Icon* instance); | ||||
| 
 | ||||
| /** Get Icon XBM bitmap data
 | ||||
|  * | ||||
|  * @param[in]  instance  pointer to Icon data | ||||
|  * | ||||
|  * @return     pointer to XBM bitmap data | ||||
|  */ | ||||
| const uint8_t* icon_get_data(const Icon* instance); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
|  | ||||
| @ -2,31 +2,32 @@ | ||||
| #include "icon_i.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <timers.h> | ||||
| 
 | ||||
| IconAnimation* icon_animation_alloc(const Icon* icon) { | ||||
|     furi_assert(icon); | ||||
|     IconAnimation* instance = furi_alloc(sizeof(IconAnimation)); | ||||
|     instance->icon = icon; | ||||
|     instance->timer = osTimerNew(icon_animation_timer_callback, osTimerPeriodic, instance, NULL); | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void icon_animation_free(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     furi_check(osTimerDelete(instance->timer) == osOK); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| const uint8_t* icon_animation_get_data(IconAnimation* instance) { | ||||
| void icon_animation_set_update_callback( | ||||
|     IconAnimation* instance, | ||||
|     IconAnimationCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(instance); | ||||
|     if(instance->tick) { | ||||
|         uint32_t now = osKernelGetTickCount(); | ||||
|         if(now < instance->tick) { | ||||
|             instance->tick = now; | ||||
|             icon_animation_next_frame(instance); | ||||
|         } else if(now - instance->tick > osKernelGetTickFreq() / instance->icon->frame_rate) { | ||||
|             instance->tick = now; | ||||
|             icon_animation_next_frame(instance); | ||||
|         } | ||||
|     instance->callback = callback; | ||||
|     instance->callback_context = context; | ||||
| } | ||||
| 
 | ||||
| const uint8_t* icon_animation_get_data(IconAnimation* instance) { | ||||
|     return instance->icon->frames[instance->frame]; | ||||
| } | ||||
| 
 | ||||
| @ -35,6 +36,19 @@ void icon_animation_next_frame(IconAnimation* instance) { | ||||
|     instance->frame = (instance->frame + 1) % instance->icon->frame_count; | ||||
| } | ||||
| 
 | ||||
| void icon_animation_timer_callback(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     IconAnimation* instance = context; | ||||
| 
 | ||||
|     if(!instance->animating) return; | ||||
| 
 | ||||
|     icon_animation_next_frame(instance); | ||||
|     if(instance->callback) { | ||||
|         instance->callback(instance, instance->callback_context); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| uint8_t icon_animation_get_width(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->icon->width; | ||||
| @ -45,30 +59,24 @@ uint8_t icon_animation_get_height(IconAnimation* instance) { | ||||
|     return instance->icon->height; | ||||
| } | ||||
| 
 | ||||
| bool icon_animation_is_animated(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->icon->frame_count > 1; | ||||
| } | ||||
| 
 | ||||
| bool icon_animation_is_animating(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->tick > 0; | ||||
| } | ||||
| 
 | ||||
| void icon_animation_start(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     instance->tick = osKernelGetTickCount(); | ||||
|     if(!instance->animating) { | ||||
|         instance->animating = true; | ||||
|         furi_check( | ||||
|             xTimerChangePeriod( | ||||
|                 instance->timer, (osKernelGetTickFreq() / instance->icon->frame_rate), 0) == | ||||
|             pdPASS); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void icon_animation_stop(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     instance->tick = 0; | ||||
|     if(instance->animating) { | ||||
|         instance->animating = false; | ||||
|         furi_check(xTimerStop(instance->timer, 0) == pdPASS); | ||||
|         instance->frame = 0; | ||||
|     } | ||||
| 
 | ||||
| uint8_t icon_animation_get_current_frame(IconAnimation* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->frame; | ||||
| } | ||||
| 
 | ||||
| bool icon_animation_is_last_frame(IconAnimation* instance) { | ||||
|  | ||||
| @ -1,64 +1,87 @@ | ||||
| /**
 | ||||
|  * @file icon_animation.h | ||||
|  * GUI: IconAnimation API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <assets_icons.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #include <assets_icons.h> | ||||
| 
 | ||||
| /** Icon Animation */ | ||||
| typedef struct IconAnimation IconAnimation; | ||||
| 
 | ||||
| /*
 | ||||
|  * Allocate icon animation instance with const icon data. | ||||
| /** Icon Animation Callback. Used for update notification */ | ||||
| typedef void (*IconAnimationCallback)(IconAnimation* instance, void* context); | ||||
| 
 | ||||
| /** Allocate icon animation instance with const icon data.
 | ||||
|  *  | ||||
|  * always returns Icon or stops system if not enough memory | ||||
|  * | ||||
|  * @param[in]  icon  pointer to Icon data | ||||
|  * | ||||
|  * @return     IconAnimation instance | ||||
|  */ | ||||
| IconAnimation* icon_animation_alloc(const Icon* icon); | ||||
| 
 | ||||
| /*
 | ||||
|  * Release icon animation instance | ||||
| /** Release icon animation instance
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  */ | ||||
| void icon_animation_free(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get icon animation width | ||||
| /** Set IconAnimation update callback
 | ||||
|  * | ||||
|  * Normally you do not need to use this function, use view_tie_icon_animation | ||||
|  * instead. | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  * @param[in]  callback  IconAnimationCallback | ||||
|  * @param      context   callback context | ||||
|  */ | ||||
| void icon_animation_set_update_callback( | ||||
|     IconAnimation* instance, | ||||
|     IconAnimationCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| /** Get icon animation width
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  * | ||||
|  * @return     width in pixels | ||||
|  */ | ||||
| uint8_t icon_animation_get_width(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get icon animation height | ||||
| /** Get icon animation height
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  * | ||||
|  * @return     height in pixels | ||||
|  */ | ||||
| uint8_t icon_animation_get_height(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Check if icon is animated | ||||
|  */ | ||||
| bool icon_animation_is_animated(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Check if icon animation is active | ||||
|  */ | ||||
| bool icon_animation_is_animating(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Start icon animation | ||||
| /** Start icon animation
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  */ | ||||
| void icon_animation_start(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Stop icon animation | ||||
| /** Stop icon animation
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  */ | ||||
| void icon_animation_stop(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Get current frame | ||||
|  */ | ||||
| uint8_t icon_animation_get_current_frame(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Returns true if current frame is a last one | ||||
| /** Returns true if current frame is a last one
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  * | ||||
|  * @return     true if last frame | ||||
|  */ | ||||
| bool icon_animation_is_last_frame(IconAnimation* instance); | ||||
| 
 | ||||
|  | ||||
| @ -1,21 +1,39 @@ | ||||
| /**
 | ||||
|  * @file icon_animation_i.h | ||||
|  * GUI: internal IconAnimation API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "icon_animation.h" | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| struct IconAnimation { | ||||
|     const Icon* icon; | ||||
|     uint8_t frame; | ||||
|     uint32_t tick; | ||||
|     bool animating; | ||||
|     osTimerId_t timer; | ||||
|     IconAnimationCallback callback; | ||||
|     void* callback_context; | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * Get pointer to current frame data | ||||
| /** Get pointer to current frame data
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  * | ||||
|  * @return     pointer to current frame XBM bitmap data | ||||
|  */ | ||||
| const uint8_t* icon_animation_get_data(IconAnimation* instance); | ||||
| 
 | ||||
| /*
 | ||||
|  * Advance to next frame | ||||
| /** Advance to next frame
 | ||||
|  * | ||||
|  * @param      instance  IconAnimation instance | ||||
|  */ | ||||
| void icon_animation_next_frame(IconAnimation* instance); | ||||
| 
 | ||||
| /** IconAnimation timer callback
 | ||||
|  * | ||||
|  * @param      context  pointer to IconAnimation | ||||
|  */ | ||||
| void icon_animation_timer_callback(void* context); | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file icon_i.h | ||||
|  * GUI: internal Icon API | ||||
|  */ | ||||
| 
 | ||||
| #include "icon.h" | ||||
| 
 | ||||
| struct Icon { | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| /**
 | ||||
|  * @file button_menu.h | ||||
|  * GUI: ButtonMenu view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| @ -6,39 +12,47 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* ButtonMenu anonymous structure */ | ||||
| /** ButtonMenu anonymous structure */ | ||||
| typedef struct ButtonMenu ButtonMenu; | ||||
| 
 | ||||
| /** ButtonMenuItem anonymous structure */ | ||||
| typedef struct ButtonMenuItem ButtonMenuItem; | ||||
| 
 | ||||
| /* Callback for any button menu actions */ | ||||
| /** Callback for any button menu actions */ | ||||
| typedef void (*ButtonMenuItemCallback)(void* context, int32_t index, InputType type); | ||||
| 
 | ||||
| /* Type of button. Difference in drawing buttons. */ | ||||
| /** Type of button. Difference in drawing buttons. */ | ||||
| typedef enum { | ||||
|     ButtonMenuItemTypeCommon, | ||||
|     ButtonMenuItemTypeControl, | ||||
| } ButtonMenuItemType; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get button menu view | ||||
|  * @param button_menu - ButtonMenu instance | ||||
| /** Get button menu view
 | ||||
|  * | ||||
|  * @param      button_menu  ButtonMenu instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* button_menu_get_view(ButtonMenu* button_menu); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Clean button menu | ||||
|  * @param button_menu - ButtonMenu instance | ||||
| /** Clean button menu
 | ||||
|  * | ||||
|  * @param      button_menu  ButtonMenu instance | ||||
|  */ | ||||
| void button_menu_clean(ButtonMenu* button_menu); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Add item to button menu instance | ||||
|  * @param button_menu   - ButtonMenu instance | ||||
|  * @param label         - text inside new button | ||||
|  * @param index         - value to distinct between buttons inside ButtonMenuItemCallback | ||||
|  * @param type          - type of button to create. Differ by button drawing. | ||||
|  *                      Control buttons have no frames, and have more squared borders. | ||||
| /** Add item to button menu instance
 | ||||
|  * | ||||
|  * @param      button_menu       ButtonMenu instance | ||||
|  * @param      label             text inside new button | ||||
|  * @param      index             value to distinct between buttons inside | ||||
|  *                               ButtonMenuItemCallback | ||||
|  * @param      callback          The callback | ||||
|  * @param      type              type of button to create. Differ by button | ||||
|  *                               drawing. Control buttons have no frames, and | ||||
|  *                               have more squared borders. | ||||
|  * @param      callback_context  The callback context | ||||
|  * | ||||
|  * @return     pointer to just-created item | ||||
|  */ | ||||
| ButtonMenuItem* button_menu_add_item( | ||||
| @ -49,29 +63,29 @@ ButtonMenuItem* button_menu_add_item( | ||||
|     ButtonMenuItemType type, | ||||
|     void* callback_context); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Allocate and initialize new instance of ButtonMenu model | ||||
| /** Allocate and initialize new instance of ButtonMenu model
 | ||||
|  * | ||||
|  * @return     just-created ButtonMenu model | ||||
|  */ | ||||
| ButtonMenu* button_menu_alloc(void); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Free ButtonMenu element | ||||
|  * @param button_menu - ButtonMenu instance | ||||
| /** Free ButtonMenu element
 | ||||
|  * | ||||
|  * @param      button_menu  ButtonMenu instance | ||||
|  */ | ||||
| void button_menu_free(ButtonMenu* button_menu); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set ButtonMenu header on top of canvas | ||||
|  * @param button_menu   - ButtonMenu instance | ||||
|  * @param header        - header on the top of button menu | ||||
| /** Set ButtonMenu header on top of canvas
 | ||||
|  * | ||||
|  * @param      button_menu  ButtonMenu instance | ||||
|  * @param      header       header on the top of button menu | ||||
|  */ | ||||
| void button_menu_set_header(ButtonMenu* button_menu, const char* header); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set selected item | ||||
|  * @param button_menu   - ButtonMenu instance | ||||
|  * @param index         - index of ButtonMenu to be selected | ||||
| /** Set selected item
 | ||||
|  * | ||||
|  * @param      button_menu  ButtonMenu instance | ||||
|  * @param      index        index of ButtonMenu to be selected | ||||
|  */ | ||||
| void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index); | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| /**
 | ||||
|  * @file button_panel.h | ||||
|  * GUI: ButtonPanel view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| @ -10,37 +16,39 @@ typedef struct ButtonPanel ButtonPanel; | ||||
| 
 | ||||
| /** Callback type to call for handling selecting button_panel items */ | ||||
| typedef void (*ButtonItemCallback)(void* context, uint32_t index); | ||||
| 
 | ||||
| /** Callback type for additional drawings above main button_panel screen */ | ||||
| typedef void (*ButtonPanelDrawCallback)(Canvas* canvas, void* _model); | ||||
| 
 | ||||
| /** Callback type to intercept input events of button_panel */ | ||||
| typedef bool (*ButtonPanelInputCallback)(InputEvent* event, void* context); | ||||
| 
 | ||||
| /** Allocate new button_panel module.
 | ||||
|  * | ||||
|  * @return  just-created module | ||||
|  * @return     ButtonPanel instance | ||||
|  */ | ||||
| ButtonPanel* button_panel_alloc(void); | ||||
| 
 | ||||
| /** Free button_panel module.
 | ||||
|  * | ||||
|  * @param   button_panel - module to free | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  */ | ||||
| void button_panel_free(ButtonPanel* button_panel); | ||||
| 
 | ||||
| /** Free items from button_panel module. Preallocated matrix stays unchanged.
 | ||||
|  * | ||||
|  * @param   button_panel - module to clean | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  */ | ||||
| void button_panel_clean(ButtonPanel* button_panel); | ||||
| 
 | ||||
| /** Reserve space for adding items.
 | ||||
|  * | ||||
|  * One does not simply use button_panel_add_item() without this function. | ||||
|  * It should be allocated space for it first. | ||||
|  * One does not simply use button_panel_add_item() without this function. It | ||||
|  * should be allocated space for it first. | ||||
|  * | ||||
|  * @param   button_panel - module to modify | ||||
|  * @param   reserve_x - number of columns in button_panel | ||||
|  * @param   reserve_y - number of rows in button_panel | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  * @param      reserve_x     number of columns in button_panel | ||||
|  * @param      reserve_y     number of rows in button_panel | ||||
|  */ | ||||
| void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t reserve_y); | ||||
| 
 | ||||
| @ -48,20 +56,20 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re | ||||
|  * | ||||
|  * Have to set element in bounds of allocated size by X and by Y. | ||||
|  * | ||||
|  * @param   button_panel - module | ||||
|  * @param   index - value to pass to callback | ||||
|  * @param   matrix_place_x - coordinates by x-axis on virtual grid, it | ||||
|  * @param      button_panel        ButtonPanel instance | ||||
|  * @param      index               value to pass to callback | ||||
|  * @param      matrix_place_x      coordinates by x-axis on virtual grid, it | ||||
|  *                                 is only used for naviagation | ||||
|  * @param   matrix_place_y - coordinates by y-axis on virtual grid, it | ||||
|  * @param      matrix_place_y      coordinates by y-axis on virtual grid, it | ||||
|  *                                 is only used for naviagation | ||||
|  * @param   x - x-coordinate to draw icon on | ||||
|  * @param   y - y-coordinate to draw icon on | ||||
|  * @param   icon_name - name of the icon to draw | ||||
|  * @param   icon_name_selected - name of the icon to draw when current | ||||
|  * @param      x                   x-coordinate to draw icon on | ||||
|  * @param      y                   y-coordinate to draw icon on | ||||
|  * @param      icon_name           name of the icon to draw | ||||
|  * @param      icon_name_selected  name of the icon to draw when current | ||||
|  *                                 element is selected | ||||
|  * @param   callback - function to call when specific element is selected | ||||
|  *                     (pressed Ok on selected item) | ||||
|  * @param   callback_context - context to pass to callback | ||||
|  * @param      callback            function to call when specific element is | ||||
|  *                                 selected (pressed Ok on selected item) | ||||
|  * @param      callback_context    context to pass to callback | ||||
|  */ | ||||
| void button_panel_add_item( | ||||
|     ButtonPanel* button_panel, | ||||
| @ -77,17 +85,19 @@ void button_panel_add_item( | ||||
| 
 | ||||
| /** Get button_panel view.
 | ||||
|  * | ||||
|  * @param   button_panel - module to get view from | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  * | ||||
|  * @return     acquired view | ||||
|  */ | ||||
| View* button_panel_get_view(ButtonPanel* button_panel); | ||||
| 
 | ||||
| /** Add label to button_panel module.
 | ||||
|  * | ||||
|  * @param   x - x-coordinate to place label | ||||
|  * @param   y - y-coordinate to place label | ||||
|  * @param   font - font to write label with | ||||
|  * @param   label_str - string label to write | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  * @param      x             x-coordinate to place label | ||||
|  * @param      y             y-coordinate to place label | ||||
|  * @param      font          font to write label with | ||||
|  * @param      label_str     string label to write | ||||
|  */ | ||||
| void button_panel_add_label( | ||||
|     ButtonPanel* button_panel, | ||||
| @ -101,9 +111,9 @@ void button_panel_add_label( | ||||
|  * | ||||
|  * Used to add popup drawings after main draw callback is done. | ||||
|  * | ||||
|  * @param   button_panel - module to modify | ||||
|  * @param   callback - callback function to set for draw event | ||||
|  * @param   context - context to pass to callback | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  * @param      callback      callback function to set for draw event | ||||
|  * @param      context       context to pass to callback | ||||
|  */ | ||||
| void button_panel_set_popup_draw_callback( | ||||
|     ButtonPanel* button_panel, | ||||
| @ -112,12 +122,12 @@ void button_panel_set_popup_draw_callback( | ||||
| 
 | ||||
| /** Set popup input callback for button_panel module.
 | ||||
|  * | ||||
|  * Used to add popup input callback. It will intercept all input | ||||
|  * events for current view. | ||||
|  * Used to add popup input callback. It will intercept all input events for | ||||
|  * current view. | ||||
|  * | ||||
|  * @param   button_panel - module to modify | ||||
|  * @param   callback - function to overwrite main input callbacks | ||||
|  * @param   context - context to pass to callback | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  * @param      callback      function to overwrite main input callbacks | ||||
|  * @param      context       context to pass to callback | ||||
|  */ | ||||
| void button_panel_set_popup_input_callback( | ||||
|     ButtonPanel* button_panel, | ||||
|  | ||||
| @ -1,52 +1,46 @@ | ||||
| /**
 | ||||
|  * @file byte_input.h | ||||
|  * GUI: ByteInput keyboard view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Byte input anonymous structure  | ||||
|  *  | ||||
|  */ | ||||
| /** Byte input anonymous structure  */ | ||||
| typedef struct ByteInput ByteInput; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief callback that is executed on save button press | ||||
|  *  | ||||
|  */ | ||||
| /** callback that is executed on save button press */ | ||||
| typedef void (*ByteInputCallback)(void* context); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief callback that is executed when byte buffer is changed | ||||
|  *  | ||||
|  */ | ||||
| /** callback that is executed when byte buffer is changed */ | ||||
| typedef void (*ByteChangedCallback)(void* context); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Allocate and initialize byte input. This byte input is used to enter bytes. | ||||
| /** Allocate and initialize byte input. This byte input is used to enter bytes.
 | ||||
|  * | ||||
|  * @return     ByteInput instance pointer | ||||
|  */ | ||||
| ByteInput* byte_input_alloc(); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Deinitialize and free byte input | ||||
| /** Deinitialize and free byte input
 | ||||
|  * | ||||
|  * @param      byte_input  Byte input instance | ||||
|  */ | ||||
| void byte_input_free(ByteInput* byte_input); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Get byte input view | ||||
| /** Get byte input view
 | ||||
|  * | ||||
|  * @param      byte_input  byte input instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* byte_input_get_view(ByteInput* byte_input); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Set byte input result callback | ||||
| /** Set byte input result callback
 | ||||
|  * | ||||
|  * @param      byte_input        byte input instance | ||||
|  * @param      input_callback    input callback fn | ||||
| @ -63,8 +57,7 @@ void byte_input_set_result_callback( | ||||
|     uint8_t* bytes, | ||||
|     uint8_t bytes_count); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set byte input header text | ||||
| /** Set byte input header text
 | ||||
|  * | ||||
|  * @param      byte_input  byte input instance | ||||
|  * @param      text        text to be shown | ||||
|  | ||||
| @ -1,74 +1,92 @@ | ||||
| /**
 | ||||
|  * @file dialog.h | ||||
|  * GUI: Dialog view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Dialog anonymous structure */ | ||||
| /** Dialog anonymous structure */ | ||||
| typedef struct Dialog Dialog; | ||||
| 
 | ||||
| /* Dialog result */ | ||||
| /** Dialog result */ | ||||
| typedef enum { | ||||
|     DialogResultLeft, | ||||
|     DialogResultRight, | ||||
|     DialogResultBack, | ||||
| } DialogResult; | ||||
| 
 | ||||
| /* Dialog result callback type
 | ||||
| /** Dialog result callback type
 | ||||
|  * @warning    comes from GUI thread | ||||
|  */ | ||||
| typedef void (*DialogResultCallback)(DialogResult result, void* context); | ||||
| 
 | ||||
| /* Allocate and initialize dialog
 | ||||
| /** Allocate and initialize dialog
 | ||||
|  * | ||||
|  * This dialog used to ask simple questions like Yes/ | ||||
|  * | ||||
|  * @return     Dialog instance | ||||
|  */ | ||||
| Dialog* dialog_alloc(); | ||||
| 
 | ||||
| /* Deinitialize and free dialog
 | ||||
|  * @param dialog - Dialog instance | ||||
| /** Deinitialize and free dialog
 | ||||
|  * | ||||
|  * @param      dialog  Dialog instance | ||||
|  */ | ||||
| void dialog_free(Dialog* dialog); | ||||
| 
 | ||||
| /* Get dialog view
 | ||||
|  * @param dialog - Dialog instance | ||||
| /** Get dialog view
 | ||||
|  * | ||||
|  * @param      dialog  Dialog instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* dialog_get_view(Dialog* dialog); | ||||
| 
 | ||||
| /* Set dialog result callback
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param callback - result callback function | ||||
| /** Set dialog result callback
 | ||||
|  * | ||||
|  * @param      dialog    Dialog instance | ||||
|  * @param      callback  result callback function | ||||
|  */ | ||||
| void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback); | ||||
| 
 | ||||
| /* Set dialog context
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param context - context pointer, will be passed to result callback | ||||
| /** Set dialog context
 | ||||
|  * | ||||
|  * @param      dialog   Dialog instance | ||||
|  * @param      context  context pointer, will be passed to result callback | ||||
|  */ | ||||
| void dialog_set_context(Dialog* dialog, void* context); | ||||
| 
 | ||||
| /* Set dialog header text
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param text - text to be shown | ||||
| /** Set dialog header text
 | ||||
|  * | ||||
|  * @param      dialog  Dialog instance | ||||
|  * @param      text    text to be shown | ||||
|  */ | ||||
| void dialog_set_header_text(Dialog* dialog, const char* text); | ||||
| 
 | ||||
| /* Set dialog text
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param text - text to be shown | ||||
| /** Set dialog text
 | ||||
|  * | ||||
|  * @param      dialog  Dialog instance | ||||
|  * @param      text    text to be shown | ||||
|  */ | ||||
| void dialog_set_text(Dialog* dialog, const char* text); | ||||
| 
 | ||||
| /* Set left button text
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param text - text to be shown | ||||
| /** Set left button text
 | ||||
|  * | ||||
|  * @param      dialog  Dialog instance | ||||
|  * @param      text    text to be shown | ||||
|  */ | ||||
| void dialog_set_left_button_text(Dialog* dialog, const char* text); | ||||
| 
 | ||||
| /* Set right button text
 | ||||
|  * @param dialog - Dialog instance | ||||
|  * @param text - text to be shown | ||||
| /** Set right button text
 | ||||
|  * | ||||
|  * @param      dialog  Dialog instance | ||||
|  * @param      text    text to be shown | ||||
|  */ | ||||
| void dialog_set_right_button_text(Dialog* dialog, const char* text); | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| /**
 | ||||
|  * @file dialog_ex.h | ||||
|  * GUI: DialogEx view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| @ -21,40 +27,51 @@ typedef enum { | ||||
| typedef void (*DialogExResultCallback)(DialogExResult result, void* context); | ||||
| 
 | ||||
| /** Allocate and initialize dialog
 | ||||
|  * | ||||
|  * This dialog used to ask simple questions | ||||
|  * | ||||
|  * @return     DialogEx instance | ||||
|  */ | ||||
| DialogEx* dialog_ex_alloc(); | ||||
| 
 | ||||
| /** Deinitialize and free dialog
 | ||||
|  * @param dialog - DialogEx instance | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  */ | ||||
| void dialog_ex_free(DialogEx* dialog_ex); | ||||
| 
 | ||||
| /** Get dialog view
 | ||||
|  * @param dialog - DialogEx instance | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* dialog_ex_get_view(DialogEx* dialog_ex); | ||||
| 
 | ||||
| /** Set dialog result callback
 | ||||
|  * @param dialog_ex - DialogEx instance | ||||
|  * @param callback - result callback function | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * @param      callback   result callback function | ||||
|  */ | ||||
| void dialog_ex_set_result_callback(DialogEx* dialog_ex, DialogExResultCallback callback); | ||||
| 
 | ||||
| /** Set dialog context
 | ||||
|  * @param dialog_ex - DialogEx instance | ||||
|  * @param context - context pointer, will be passed to result callback | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * @param      context    context pointer, will be passed to result callback | ||||
|  */ | ||||
| void dialog_ex_set_context(DialogEx* dialog_ex, void* context); | ||||
| 
 | ||||
| /** Set dialog header text
 | ||||
|  * | ||||
|  * If text is null, dialog header will not be rendered | ||||
|  * @param dialog - DialogEx instance | ||||
|  * @param text - text to be shown, can be multiline | ||||
|  * @param x, y - text position | ||||
|  * @param horizontal, vertical - text aligment | ||||
|  * | ||||
|  * @param      dialog_ex   DialogEx instance | ||||
|  * @param      text        text to be shown, can be multiline | ||||
|  * @param      x           x position | ||||
|  * @param      y           y position | ||||
|  * @param      horizontal  horizontal text aligment | ||||
|  * @param      vertical    vertical text aligment | ||||
|  */ | ||||
| void dialog_ex_set_header( | ||||
|     DialogEx* dialog_ex, | ||||
| @ -65,11 +82,15 @@ void dialog_ex_set_header( | ||||
|     Align vertical); | ||||
| 
 | ||||
| /** Set dialog text
 | ||||
|  * | ||||
|  * If text is null, dialog text will not be rendered | ||||
|  * @param dialog - DialogEx instance | ||||
|  * @param text - text to be shown, can be multiline | ||||
|  * @param x, y - text position | ||||
|  * @param horizontal, vertical - text aligment | ||||
|  * | ||||
|  * @param      dialog_ex   DialogEx instance | ||||
|  * @param      text        text to be shown, can be multiline | ||||
|  * @param      x           x position | ||||
|  * @param      y           y position | ||||
|  * @param      horizontal  horizontal text aligment | ||||
|  * @param      vertical    vertical text aligment | ||||
|  */ | ||||
| void dialog_ex_set_text( | ||||
|     DialogEx* dialog_ex, | ||||
| @ -80,35 +101,46 @@ void dialog_ex_set_text( | ||||
|     Align vertical); | ||||
| 
 | ||||
| /** Set dialog icon
 | ||||
|  * | ||||
|  * If x or y is negative, dialog icon will not be rendered | ||||
|  * @param dialog - DialogEx instance | ||||
|  * @param x, y - icon position | ||||
|  * @param name - icon to be shown | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * @param      x          x position | ||||
|  * @param      y          y position | ||||
|  * @param      icon       The icon | ||||
|  * @param      name  icon to be shown | ||||
|  */ | ||||
| void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon); | ||||
| 
 | ||||
| /** Set left button text
 | ||||
|  * | ||||
|  * If text is null, left button will not be rendered and processed | ||||
|  * @param dialog - DialogEx instance | ||||
|  * @param text - text to be shown | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * @param      text       text to be shown | ||||
|  */ | ||||
| void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text); | ||||
| 
 | ||||
| /** Set center button text
 | ||||
|  * | ||||
|  * If text is null, center button will not be rendered and processed | ||||
|  * @param dialog - DialogEx instance | ||||
|  * @param text - text to be shown | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * @param      text       text to be shown | ||||
|  */ | ||||
| void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text); | ||||
| 
 | ||||
| /** Set right button text
 | ||||
|  * | ||||
|  * If text is null, right button will not be rendered and processed | ||||
|  * @param dialog - DialogEx instance | ||||
|  * @param text - text to be shown | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  * @param      text       text to be shown | ||||
|  */ | ||||
| void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text); | ||||
| 
 | ||||
| /** Clean dialog
 | ||||
|  * | ||||
|  * @param      dialog_ex  DialogEx instance | ||||
|  */ | ||||
| void dialog_ex_clean(DialogEx* dialog_ex); | ||||
|  | ||||
| @ -1,25 +1,37 @@ | ||||
| /**
 | ||||
|  * @file empty_screen.h | ||||
|  * GUI: EmptyScreen view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Empty screen anonymous structure */ | ||||
| /** Empty screen anonymous structure */ | ||||
| typedef struct EmptyScreen EmptyScreen; | ||||
| 
 | ||||
| /* Allocate and initialize empty screen
 | ||||
| /** Allocate and initialize empty screen
 | ||||
|  * | ||||
|  * This empty screen used to ask simple questions like Yes/ | ||||
|  * | ||||
|  * @return     EmptyScreen instance | ||||
|  */ | ||||
| EmptyScreen* empty_screen_alloc(); | ||||
| 
 | ||||
| /* Deinitialize and free empty screen
 | ||||
|  * @param empty_screen - Empty screen instance | ||||
| /** Deinitialize and free empty screen
 | ||||
|  * | ||||
|  * @param      empty_screen  Empty screen instance | ||||
|  */ | ||||
| void empty_screen_free(EmptyScreen* empty_screen); | ||||
| 
 | ||||
| /* Get empty screen view
 | ||||
|  * @param empty_screen - Empty screen instance | ||||
| /** Get empty screen view
 | ||||
|  * | ||||
|  * @param      empty_screen  Empty screen instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* empty_screen_get_view(EmptyScreen* empty_screen); | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| /**
 | ||||
|  * @file file_select.h | ||||
|  * GUI: FileSelect view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
|  | ||||
							
								
								
									
										54
									
								
								applications/gui/modules/menu.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										54
									
								
								applications/gui/modules/menu.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -20,7 +20,7 @@ ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST); | ||||
| 
 | ||||
| typedef struct { | ||||
|     MenuItemArray_t items; | ||||
|     uint8_t position; | ||||
|     size_t position; | ||||
| } MenuModel; | ||||
| 
 | ||||
| static void menu_process_up(Menu* menu); | ||||
| @ -32,7 +32,7 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
| 
 | ||||
|     uint8_t position = model->position; | ||||
|     size_t position = model->position; | ||||
|     size_t items_count = MenuItemArray_size(model->items); | ||||
|     if(items_count) { | ||||
|         MenuItem* item; | ||||
| @ -43,7 +43,6 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { | ||||
|         item = MenuItemArray_get(model->items, shift_position); | ||||
|         if(item->icon) { | ||||
|             canvas_draw_icon_animation(canvas, 4, 3, item->icon); | ||||
|             icon_animation_stop(item->icon); | ||||
|         } | ||||
|         canvas_draw_str(canvas, 22, 14, item->label); | ||||
|         // Second line main
 | ||||
| @ -52,7 +51,6 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { | ||||
|         item = MenuItemArray_get(model->items, shift_position); | ||||
|         if(item->icon) { | ||||
|             canvas_draw_icon_animation(canvas, 4, 25, item->icon); | ||||
|             icon_animation_start(item->icon); | ||||
|         } | ||||
|         canvas_draw_str(canvas, 22, 36, item->label); | ||||
|         // Third line
 | ||||
| @ -61,7 +59,6 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { | ||||
|         item = MenuItemArray_get(model->items, shift_position); | ||||
|         if(item->icon) { | ||||
|             canvas_draw_icon_animation(canvas, 4, 47, item->icon); | ||||
|             icon_animation_stop(item->icon); | ||||
|         } | ||||
|         canvas_draw_str(canvas, 22, 58, item->label); | ||||
|         // Frame and scrollbar
 | ||||
| @ -93,6 +90,30 @@ static bool menu_input_callback(InputEvent* event, void* context) { | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| static void menu_enter(void* context) { | ||||
|     Menu* menu = context; | ||||
|     with_view_model( | ||||
|         menu->view, (MenuModel * model) { | ||||
|             MenuItem* item = MenuItemArray_get(model->items, model->position); | ||||
|             if(item && item->icon) { | ||||
|                 icon_animation_start(item->icon); | ||||
|             } | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void menu_exit(void* context) { | ||||
|     Menu* menu = context; | ||||
|     with_view_model( | ||||
|         menu->view, (MenuModel * model) { | ||||
|             MenuItem* item = MenuItemArray_get(model->items, model->position); | ||||
|             if(item && item->icon) { | ||||
|                 icon_animation_stop(item->icon); | ||||
|             } | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| Menu* menu_alloc() { | ||||
|     Menu* menu = furi_alloc(sizeof(Menu)); | ||||
|     menu->view = view_alloc(menu->view); | ||||
| @ -100,6 +121,8 @@ Menu* menu_alloc() { | ||||
|     view_allocate_model(menu->view, ViewModelTypeLocking, sizeof(MenuModel)); | ||||
|     view_set_draw_callback(menu->view, menu_draw_callback); | ||||
|     view_set_input_callback(menu->view, menu_input_callback); | ||||
|     view_set_enter_callback(menu->view, menu_enter); | ||||
|     view_set_exit_callback(menu->view, menu_exit); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         menu->view, (MenuModel * model) { | ||||
| @ -143,6 +166,7 @@ void menu_add_item( | ||||
|             item = MenuItemArray_push_new(model->items); | ||||
|             item->label = label; | ||||
|             item->icon = icon; | ||||
|             view_tie_icon_animation(menu->view, item->icon); | ||||
|             item->index = index; | ||||
|             item->callback = callback; | ||||
|             item->callback_context = context; | ||||
| @ -175,11 +199,21 @@ void menu_set_selected_item(Menu* menu, uint32_t index) { | ||||
| static void menu_process_up(Menu* menu) { | ||||
|     with_view_model( | ||||
|         menu->view, (MenuModel * model) { | ||||
|             MenuItem* item = MenuItemArray_get(model->items, model->position); | ||||
|             if(item && item->icon) { | ||||
|                 icon_animation_stop(item->icon); | ||||
|             } | ||||
| 
 | ||||
|             if(model->position > 0) { | ||||
|                 model->position--; | ||||
|             } else { | ||||
|                 model->position = MenuItemArray_size(model->items) - 1; | ||||
|             } | ||||
| 
 | ||||
|             item = MenuItemArray_get(model->items, model->position); | ||||
|             if(item && item->icon) { | ||||
|                 icon_animation_start(item->icon); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -187,11 +221,21 @@ static void menu_process_up(Menu* menu) { | ||||
| static void menu_process_down(Menu* menu) { | ||||
|     with_view_model( | ||||
|         menu->view, (MenuModel * model) { | ||||
|             MenuItem* item = MenuItemArray_get(model->items, model->position); | ||||
|             if(item && item->icon) { | ||||
|                 icon_animation_stop(item->icon); | ||||
|             } | ||||
| 
 | ||||
|             if(model->position < MenuItemArray_size(model->items) - 1) { | ||||
|                 model->position++; | ||||
|             } else { | ||||
|                 model->position = 0; | ||||
|             } | ||||
| 
 | ||||
|             item = MenuItemArray_get(model->items, model->position); | ||||
|             if(item && item->icon) { | ||||
|                 icon_animation_start(item->icon); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| /**
 | ||||
|  * @file menu.h | ||||
|  * GUI: Menu view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| @ -7,31 +13,38 @@ extern "C" { | ||||
| 
 | ||||
| /** Menu anonymous structure */ | ||||
| typedef struct Menu Menu; | ||||
| 
 | ||||
| /** Menu Item Callback */ | ||||
| typedef void (*MenuItemCallback)(void* context, uint32_t index); | ||||
| 
 | ||||
| /** Menu allocation and initialization
 | ||||
|  * | ||||
|  * @return     Menu instance | ||||
|  */ | ||||
| Menu* menu_alloc(); | ||||
| 
 | ||||
| /** Free menu
 | ||||
|  * @param menu - Menu instance | ||||
|  * | ||||
|  * @param      menu  Menu instance | ||||
|  */ | ||||
| void menu_free(Menu* menu); | ||||
| 
 | ||||
| /** Get Menu view
 | ||||
|  * @param menu - Menu instance | ||||
|  * | ||||
|  * @param      menu  Menu instance | ||||
|  * | ||||
|  * @return     View instance | ||||
|  */ | ||||
| View* menu_get_view(Menu* menu); | ||||
| 
 | ||||
| /** Add item to menu
 | ||||
|  * @param menu - Menu instance | ||||
|  * @param label - menu item string label | ||||
|  * @param icon - IconAnimation instance | ||||
|  * @param index - menu item index | ||||
|  * @param callback - MenuItemCallback instance | ||||
|  * @param context - pointer to context | ||||
|  * | ||||
|  * @param      menu      Menu instance | ||||
|  * @param      label     menu item string label | ||||
|  * @param      icon      IconAnimation instance | ||||
|  * @param      index     menu item index | ||||
|  * @param      callback  MenuItemCallback instance | ||||
|  * @param      context   pointer to context | ||||
|  */ | ||||
| void menu_add_item( | ||||
|     Menu* menu, | ||||
| @ -42,14 +55,16 @@ void menu_add_item( | ||||
|     void* context); | ||||
| 
 | ||||
| /** Clean menu
 | ||||
|  * Note: this function does not free menu instance | ||||
|  * @param menu - Menu instance | ||||
|  * @note       this function does not free menu instance | ||||
|  * | ||||
|  * @param      menu  Menu instance | ||||
|  */ | ||||
| void menu_clean(Menu* menu); | ||||
| 
 | ||||
| /** Set current menu item
 | ||||
|  * @param submenu | ||||
|  * @param index | ||||
|  * | ||||
|  * @param      menu   Menu instance | ||||
|  * @param      index  The index | ||||
|  */ | ||||
| void menu_set_selected_item(Menu* menu, uint32_t index); | ||||
| 
 | ||||
|  | ||||
| @ -1,52 +1,70 @@ | ||||
| /**
 | ||||
|  * @file popup.h | ||||
|  * GUI: Popup view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Popup anonymous structure */ | ||||
| /** Popup anonymous structure */ | ||||
| typedef struct Popup Popup; | ||||
| 
 | ||||
| /* Popup result callback type
 | ||||
| /** Popup result callback type
 | ||||
|  * @warning    comes from GUI thread | ||||
|  */ | ||||
| typedef void (*PopupCallback)(void* context); | ||||
| 
 | ||||
| /* Allocate and initialize popup
 | ||||
| /** Allocate and initialize popup
 | ||||
|  * | ||||
|  * This popup used to ask simple questions like Yes/ | ||||
|  * | ||||
|  * @return     Popup instance | ||||
|  */ | ||||
| Popup* popup_alloc(); | ||||
| 
 | ||||
| /* Deinitialize and free popup
 | ||||
|  * @param popup - Popup instance | ||||
| /** Deinitialize and free popup
 | ||||
|  * | ||||
|  * @param      popup  Popup instance | ||||
|  */ | ||||
| void popup_free(Popup* popup); | ||||
| 
 | ||||
| /* Get popup view
 | ||||
|  * @param popup - Popup instance | ||||
| /** Get popup view
 | ||||
|  * | ||||
|  * @param      popup  Popup instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* popup_get_view(Popup* popup); | ||||
| 
 | ||||
| /* Set popup header text
 | ||||
|  * @param popup - Popup instance | ||||
|  * @param text - text to be shown | ||||
| /** Set popup header text
 | ||||
|  * | ||||
|  * @param      popup     Popup instance | ||||
|  * @param      callback  PopupCallback | ||||
|  */ | ||||
| void popup_set_callback(Popup* popup, PopupCallback callback); | ||||
| 
 | ||||
| /* Set popup context
 | ||||
|  * @param popup - Popup instance | ||||
|  * @param context - context pointer, will be passed to result callback | ||||
| /** Set popup context
 | ||||
|  * | ||||
|  * @param      popup    Popup instance | ||||
|  * @param      context  context pointer, will be passed to result callback | ||||
|  */ | ||||
| void popup_set_context(Popup* popup, void* context); | ||||
| 
 | ||||
| /* Set popup header text
 | ||||
| /** Set popup header text
 | ||||
|  * | ||||
|  * If text is null, popup header will not be rendered | ||||
|  * @param popup - Popup instance | ||||
|  * @param text - text to be shown, can be multiline | ||||
|  * @param x, y - text position | ||||
|  * @param horizontal, vertical - text aligment | ||||
|  * | ||||
|  * @param      popup       Popup instance | ||||
|  * @param      text        text to be shown, can be multiline | ||||
|  * @param      x           x position | ||||
|  * @param      y           y position | ||||
|  * @param      horizontal  horizontal alignment | ||||
|  * @param      vertical    vertical aligment | ||||
|  */ | ||||
| void popup_set_header( | ||||
|     Popup* popup, | ||||
| @ -56,12 +74,16 @@ void popup_set_header( | ||||
|     Align horizontal, | ||||
|     Align vertical); | ||||
| 
 | ||||
| /* Set popup text
 | ||||
| /** Set popup text
 | ||||
|  * | ||||
|  * If text is null, popup text will not be rendered | ||||
|  * @param popup - Popup instance | ||||
|  * @param text - text to be shown, can be multiline | ||||
|  * @param x, y - text position | ||||
|  * @param horizontal, vertical - text aligment | ||||
|  * | ||||
|  * @param      popup       Popup instance | ||||
|  * @param      text        text to be shown, can be multiline | ||||
|  * @param      x           x position | ||||
|  * @param      y           y position | ||||
|  * @param      horizontal  horizontal alignment | ||||
|  * @param      vertical    vertical aligment | ||||
|  */ | ||||
| void popup_set_text( | ||||
|     Popup* popup, | ||||
| @ -71,27 +93,33 @@ void popup_set_text( | ||||
|     Align horizontal, | ||||
|     Align vertical); | ||||
| 
 | ||||
| /* Set popup icon
 | ||||
| /** Set popup icon
 | ||||
|  * | ||||
|  * If icon position is negative, popup icon will not be rendered | ||||
|  * @param popup - Popup instance | ||||
|  * @param x, y - icon position | ||||
|  * @param name - icon to be shown | ||||
|  * | ||||
|  * @param      popup  Popup instance | ||||
|  * @param      x      x position | ||||
|  * @param      y      y position | ||||
|  * @param      icon   pointer to Icon data | ||||
|  */ | ||||
| void popup_set_icon(Popup* popup, uint8_t x, uint8_t y, const Icon* icon); | ||||
| 
 | ||||
| /* Set popup timeout
 | ||||
|  * @param popup - Popup instance | ||||
|  * @param timeout_in_ms - popup timeout value in milliseconds | ||||
| /** Set popup timeout
 | ||||
|  * | ||||
|  * @param      popup          Popup instance | ||||
|  * @param      timeout_in_ms  popup timeout value in milliseconds | ||||
|  */ | ||||
| void popup_set_timeout(Popup* popup, uint32_t timeout_in_ms); | ||||
| 
 | ||||
| /* Enable popup timeout
 | ||||
|  * @param popup - Popup instance | ||||
| /** Enable popup timeout
 | ||||
|  * | ||||
|  * @param      popup  Popup instance | ||||
|  */ | ||||
| void popup_enable_timeout(Popup* popup); | ||||
| 
 | ||||
| /* Disable popup timeout
 | ||||
|  * @param popup - Popup instance | ||||
| /** Disable popup timeout
 | ||||
|  * | ||||
|  * @param      popup  Popup instance | ||||
|  */ | ||||
| void popup_disable_timeout(Popup* popup); | ||||
| 
 | ||||
|  | ||||
| @ -1,40 +1,50 @@ | ||||
| /**
 | ||||
|  * @file submenu.h | ||||
|  * GUI: SubMenu view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Submenu anonymous structure */ | ||||
| /** Submenu anonymous structure */ | ||||
| typedef struct Submenu Submenu; | ||||
| typedef void (*SubmenuItemCallback)(void* context, uint32_t index); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Allocate and initialize submenu | ||||
| /** Allocate and initialize submenu 
 | ||||
|  *  | ||||
|  * This submenu is used to select one option | ||||
|  * | ||||
|  * @return     Submenu instance | ||||
|  */ | ||||
| Submenu* submenu_alloc(); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Deinitialize and free submenu | ||||
|  * @param submenu - Submenu instance | ||||
| /** Deinitialize and free submenu
 | ||||
|  * | ||||
|  * @param      submenu  Submenu instance | ||||
|  */ | ||||
| void submenu_free(Submenu* submenu); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get submenu view | ||||
|  * @param submenu - Submenu instance | ||||
| /** Get submenu view
 | ||||
|  * | ||||
|  * @param      submenu  Submenu instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* submenu_get_view(Submenu* submenu); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Add item to submenu | ||||
|  * @param submenu - Submenu instance | ||||
|  * @param label - menu item label | ||||
|  * @param index - menu item index, used for callback, may be the same with other items | ||||
|  * @param callback - menu item callback | ||||
|  * @param callback_context - menu item callback context | ||||
| /** Add item to submenu
 | ||||
|  * | ||||
|  * @param      submenu           Submenu instance | ||||
|  * @param      label             menu item label | ||||
|  * @param      index             menu item index, used for callback, may be | ||||
|  *                               the same with other items | ||||
|  * @param      callback          menu item callback | ||||
|  * @param      callback_context  menu item callback context | ||||
|  */ | ||||
| void submenu_add_item( | ||||
|     Submenu* submenu, | ||||
| @ -43,23 +53,23 @@ void submenu_add_item( | ||||
|     SubmenuItemCallback callback, | ||||
|     void* callback_context); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Remove all items from submenu | ||||
|  * @param submenu - Submenu instance | ||||
| /** Remove all items from submenu
 | ||||
|  * | ||||
|  * @param      submenu  Submenu instance | ||||
|  */ | ||||
| void submenu_clean(Submenu* submenu); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set submenu item selector | ||||
|  * @param submenu | ||||
|  * @param index | ||||
| /** Set submenu item selector
 | ||||
|  * | ||||
|  * @param      submenu  Submenu instance | ||||
|  * @param      index    The index | ||||
|  */ | ||||
| void submenu_set_selected_item(Submenu* submenu, uint32_t index); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set optional header for submenu | ||||
|  * @param submenu   - submenu entity | ||||
|  * @param header    - header to set | ||||
| /** Set optional header for submenu
 | ||||
|  * | ||||
|  * @param      submenu  Submenu instance | ||||
|  * @param      header   header to set | ||||
|  */ | ||||
| void submenu_set_header(Submenu* submenu, const char* header); | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +1,17 @@ | ||||
| /**
 | ||||
|  * @file text_box.h | ||||
|  * GUI: TextBox view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* TextBox anonymous structure */ | ||||
| /** TextBox anonymous structure */ | ||||
| typedef struct TextBox TextBox; | ||||
| typedef void (*TextBoxExitCallback)(void* context); | ||||
| 
 | ||||
| @ -15,44 +21,54 @@ typedef enum { | ||||
| } TextBoxFont; | ||||
| 
 | ||||
| /** Allocate and initialize text_box
 | ||||
|  * | ||||
|  * @return     TextBox instance | ||||
|  */ | ||||
| TextBox* text_box_alloc(); | ||||
| 
 | ||||
| /** Deinitialize and free text_box
 | ||||
|  * | ||||
|  * @param      text_box  text_box instance | ||||
|  */ | ||||
| void text_box_free(TextBox* text_box); | ||||
| 
 | ||||
| /** Get text_box view
 | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* text_box_get_view(TextBox* text_box); | ||||
| 
 | ||||
| /** Clean text_box
 | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  */ | ||||
| void text_box_clean(TextBox* text_box); | ||||
| 
 | ||||
| /** Set text for text_box
 | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  * @param      text      text to set | ||||
|  */ | ||||
| void text_box_set_text(TextBox* text_box, const char* text); | ||||
| 
 | ||||
| /** Set TextBox font
 | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  * @param      font      TextBoxFont instance | ||||
|  */ | ||||
| void text_box_set_font(TextBox* text_box, TextBoxFont font); | ||||
| 
 | ||||
| /** Set text_box context
 | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  * @param      context   context pointer | ||||
|  */ | ||||
| void text_box_set_context(TextBox* text_box, void* context); | ||||
| 
 | ||||
| /** Set exit callback
 | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  * @param      callback  TextBoxExitCallback callback pointer | ||||
|  */ | ||||
|  | ||||
| @ -1,44 +1,59 @@ | ||||
| /**
 | ||||
|  * @file text_input.h | ||||
|  * GUI: TextInput keybord view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Text input anonymous structure */ | ||||
| /** Text input anonymous structure */ | ||||
| typedef struct TextInput TextInput; | ||||
| typedef void (*TextInputCallback)(void* context); | ||||
| 
 | ||||
| /** Allocate and initialize text input 
 | ||||
|  *  | ||||
|  * This text input is used to enter string | ||||
|  * | ||||
|  * @return     TextInput instance | ||||
|  */ | ||||
| TextInput* text_input_alloc(); | ||||
| 
 | ||||
| /** Deinitialize and free text input
 | ||||
|  * @param text_input - TextInput instance | ||||
|  * | ||||
|  * @param      text_input  TextInput instance | ||||
|  */ | ||||
| void text_input_free(TextInput* text_input); | ||||
| 
 | ||||
| /** Clean text input view
 | ||||
|  * Note: this function does not free memory | ||||
|  * @param text_input - Text input instance | ||||
| /** Clean text input view Note: this function does not free memory
 | ||||
|  * | ||||
|  * @param      text_input  Text input instance | ||||
|  */ | ||||
| void text_input_clean(TextInput* text_input); | ||||
| 
 | ||||
| /** Get text input view
 | ||||
|  * @param text_input - TextInput instance | ||||
|  * | ||||
|  * @param      text_input  TextInput instance | ||||
|  * | ||||
|  * @return     View instance that can be used for embedding | ||||
|  */ | ||||
| View* text_input_get_view(TextInput* text_input); | ||||
| 
 | ||||
| /** Set text input result callback
 | ||||
|  * @param text_input - TextInput instance | ||||
|  * @param callback - callback fn | ||||
|  * @param callback_context - callback context | ||||
|  * @param text_buffer - pointer to YOUR text buffer, that we going to modify | ||||
|  * @param text_buffer_size - YOUR text buffer size in bytes. Max string length will be text_buffer_size - 1. | ||||
|  * @param clear_default_text - clear text from text_buffer on first OK event | ||||
|  * | ||||
|  * @param      text_input          TextInput instance | ||||
|  * @param      callback            callback fn | ||||
|  * @param      callback_context    callback context | ||||
|  * @param      text_buffer         pointer to YOUR text buffer, that we going | ||||
|  *                                 to modify | ||||
|  * @param      text_buffer_size    YOUR text buffer size in bytes. Max string | ||||
|  *                                 length will be text_buffer_size-1. | ||||
|  * @param      clear_default_text  clear text from text_buffer on first OK | ||||
|  *                                 event | ||||
|  */ | ||||
| void text_input_set_result_callback( | ||||
|     TextInput* text_input, | ||||
| @ -49,8 +64,9 @@ void text_input_set_result_callback( | ||||
|     bool clear_default_text); | ||||
| 
 | ||||
| /** Set text input header text
 | ||||
|  * @param text_input - TextInput instance | ||||
|  * @param text - text to be shown | ||||
|  * | ||||
|  * @param      text_input  TextInput instance | ||||
|  * @param      text        text to be shown | ||||
|  */ | ||||
| void text_input_set_header_text(TextInput* text_input, const char* text); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										29
									
								
								applications/gui/modules/variable-item-list.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										29
									
								
								applications/gui/modules/variable-item-list.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -18,6 +18,8 @@ ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); | ||||
| 
 | ||||
| struct VariableItemList { | ||||
|     View* view; | ||||
|     VariableItemListEnterCallback callback; | ||||
|     void* context; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -30,6 +32,7 @@ static void variable_item_list_process_up(VariableItemList* variable_item_list); | ||||
| static void variable_item_list_process_down(VariableItemList* variable_item_list); | ||||
| static void variable_item_list_process_left(VariableItemList* variable_item_list); | ||||
| static void variable_item_list_process_right(VariableItemList* variable_item_list); | ||||
| static void variable_item_list_process_ok(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { | ||||
|     VariableItemListModel* model = _model; | ||||
| @ -104,6 +107,9 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context) | ||||
|             consumed = true; | ||||
|             variable_item_list_process_right(variable_item_list); | ||||
|             break; | ||||
|         case InputKeyOk: | ||||
|             variable_item_list_process_ok(variable_item_list); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
| @ -198,6 +204,16 @@ void variable_item_list_process_right(VariableItemList* variable_item_list) { | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void variable_item_list_process_ok(VariableItemList* variable_item_list) { | ||||
|     with_view_model( | ||||
|         variable_item_list->view, (VariableItemListModel * model) { | ||||
|             if(variable_item_list->callback) { | ||||
|                 variable_item_list->callback(variable_item_list->context, model->position); | ||||
|             } | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| VariableItemList* variable_item_list_alloc() { | ||||
|     VariableItemList* variable_item_list = furi_alloc(sizeof(VariableItemList)); | ||||
|     variable_item_list->view = view_alloc(); | ||||
| @ -280,6 +296,19 @@ VariableItem* variable_item_list_add( | ||||
|     return item; | ||||
| } | ||||
| 
 | ||||
| void variable_item_list_set_enter_callback( | ||||
|     VariableItemList* variable_item_list, | ||||
|     VariableItemListEnterCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(callback); | ||||
|     with_view_model( | ||||
|         variable_item_list->view, (VariableItemListModel * model) { | ||||
|             variable_item_list->callback = callback; | ||||
|             variable_item_list->context = context; | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index) { | ||||
|     item->current_value_index = current_value_index; | ||||
| } | ||||
|  | ||||
							
								
								
									
										42
									
								
								applications/gui/modules/variable-item-list.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										42
									
								
								applications/gui/modules/variable-item-list.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,4 +1,10 @@ | ||||
| /**
 | ||||
|  * @file variable-item-list.h | ||||
|  * GUI: VariableItemList view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| @ -8,27 +14,42 @@ extern "C" { | ||||
| typedef struct VariableItemList VariableItemList; | ||||
| typedef struct VariableItem VariableItem; | ||||
| typedef void (*VariableItemChangeCallback)(VariableItem* item); | ||||
| typedef void (*VariableItemListEnterCallback)(void* context, uint32_t index); | ||||
| 
 | ||||
| /** Allocate and initialize VariableItemList
 | ||||
|  * | ||||
|  * @return     VariableItemList* | ||||
|  */ | ||||
| VariableItemList* variable_item_list_alloc(); | ||||
| 
 | ||||
| /** Deinitialize and free VariableItemList
 | ||||
|  * | ||||
|  * @param      variable_item_list  VariableItemList instance | ||||
|  */ | ||||
| void variable_item_list_free(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| /** Clear all elements from list
 | ||||
|  * | ||||
|  * @param      variable_item_list  VariableItemList instance | ||||
|  */ | ||||
| void variable_item_list_clean(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| /** Get VariableItemList View instance
 | ||||
|  * | ||||
|  * @param      variable_item_list  VariableItemList instance | ||||
|  * | ||||
|  * @return     View instance | ||||
|  */ | ||||
| View* variable_item_list_get_view(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| /** Add item to VariableItemList
 | ||||
|  * | ||||
|  * @param      variable_item_list  VariableItemList instance | ||||
|  * @param      label               item name | ||||
|  * @param      values_count        item values count | ||||
|  * @param      change_callback     called on value change in gui | ||||
|  * @param      context             item context | ||||
|  * | ||||
|  * @return     VariableItem* item instance | ||||
|  */ | ||||
| VariableItem* variable_item_list_add( | ||||
| @ -38,26 +59,43 @@ VariableItem* variable_item_list_add( | ||||
|     VariableItemChangeCallback change_callback, | ||||
|     void* context); | ||||
| 
 | ||||
| /** Set enter callback
 | ||||
|  * | ||||
|  * @param      variable_item_list  VariableItemList instance | ||||
|  * @param      callback            VariableItemListEnterCallback instance | ||||
|  * @param      context             pointer to context | ||||
|  */ | ||||
| void variable_item_list_set_enter_callback( | ||||
|     VariableItemList* variable_item_list, | ||||
|     VariableItemListEnterCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| /** Set item current selected index
 | ||||
|  * | ||||
|  * @param      item                 VariableItem* instance | ||||
|  * @param current_value_index  | ||||
|  * @param      current_value_index  The current value index | ||||
|  */ | ||||
| void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index); | ||||
| 
 | ||||
| /** Set item current selected text
 | ||||
|  * | ||||
|  * @param      item                VariableItem* instance | ||||
|  * @param current_value_text  | ||||
|  * @param      current_value_text  The current value text | ||||
|  */ | ||||
| void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text); | ||||
| 
 | ||||
| /** Get item current selected index
 | ||||
|  * | ||||
|  * @param      item  VariableItem* instance | ||||
|  * | ||||
|  * @return     uint8_t current selected index | ||||
|  */ | ||||
| uint8_t variable_item_get_current_value_index(VariableItem* item); | ||||
| 
 | ||||
| /** Get item context
 | ||||
|  * | ||||
|  * @param      item  VariableItem* instance | ||||
|  * | ||||
|  * @return     void* item context | ||||
|  */ | ||||
| void* variable_item_get_context(VariableItem* item); | ||||
|  | ||||
| @ -1,38 +1,51 @@ | ||||
| /**
 | ||||
|  * @file widget.h | ||||
|  * GUI: Widget view module API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "widget_elements/widget_element_i.h" | ||||
| 
 | ||||
| typedef struct Widget Widget; | ||||
| typedef struct WidgetElement WidgetElement; | ||||
| 
 | ||||
| /** Allocate Widget that holds Widget Elements
 | ||||
|  * | ||||
|  * @return     Widget instance | ||||
|  */ | ||||
| Widget* widget_alloc(); | ||||
| 
 | ||||
| /** Free Widget
 | ||||
|  * @note       this function free allocated Widget Elements | ||||
|  * | ||||
|  * @param      widget  Widget instance | ||||
|  */ | ||||
| void widget_free(Widget* widget); | ||||
| 
 | ||||
| /** Clear Widget
 | ||||
|  * | ||||
|  * @param      widget  Widget instance | ||||
|  */ | ||||
| void widget_clear(Widget* widget); | ||||
| 
 | ||||
| /** Get Widget view
 | ||||
|  * | ||||
|  * @param      widget  Widget instance | ||||
|  * | ||||
|  * @return     View instance | ||||
|  */ | ||||
| View* widget_get_view(Widget* widget); | ||||
| 
 | ||||
| /** Add Multi String Element
 | ||||
|  * | ||||
|  * @param      widget      Widget instance | ||||
|  * @param x - x coordinate | ||||
|  * @param y - y coordinate | ||||
|  * @param horizontal - Align instance | ||||
|  * @param vertical - Align instance | ||||
|  * @param      x           x coordinate | ||||
|  * @param      y           y coordinate | ||||
|  * @param      horizontal  Align instance | ||||
|  * @param      vertical    Align instance | ||||
|  * @param      font        Font instance | ||||
|  * @param[in]  text        The text | ||||
|  */ | ||||
| void widget_add_string_multiline_element( | ||||
|     Widget* widget, | ||||
| @ -44,12 +57,14 @@ void widget_add_string_multiline_element( | ||||
|     const char* text); | ||||
| 
 | ||||
| /** Add String Element
 | ||||
|  * | ||||
|  * @param      widget      Widget instance | ||||
|  * @param x - x coordinate | ||||
|  * @param y - y coordinate | ||||
|  * @param horizontal - Align instance | ||||
|  * @param vertical - Align instance | ||||
|  * @param      x           x coordinate | ||||
|  * @param      y           y coordinate | ||||
|  * @param      horizontal  Align instance | ||||
|  * @param      vertical    Align instance | ||||
|  * @param      font        Font instance | ||||
|  * @param[in]  text        The text | ||||
|  */ | ||||
| void widget_add_string_element( | ||||
|     Widget* widget, | ||||
| @ -61,6 +76,7 @@ void widget_add_string_element( | ||||
|     const char* text); | ||||
| 
 | ||||
| /** Add Button Element
 | ||||
|  * | ||||
|  * @param      widget       Widget instance | ||||
|  * @param      button_type  GuiButtonType instance | ||||
|  * @param      text         text on allocated button | ||||
| @ -75,6 +91,7 @@ void widget_add_button_element( | ||||
|     void* context); | ||||
| 
 | ||||
| /** Add Icon Element
 | ||||
|  * | ||||
|  * @param      widget  Widget instance | ||||
|  * @param      x       top left x coordinate | ||||
|  * @param      y       top left y coordinate | ||||
| @ -83,6 +100,7 @@ void widget_add_button_element( | ||||
| void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon); | ||||
| 
 | ||||
| /** Add Frame Element
 | ||||
|  * | ||||
|  * @param      widget  Widget instance | ||||
|  * @param      x       top left x coordinate | ||||
|  * @param      y       top left y coordinate | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file widget_element_i.h | ||||
|  * GUI: internal Widget Element API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <gui/view.h> | ||||
| @ -29,7 +34,7 @@ struct WidgetElement { | ||||
|     Widget* parent; | ||||
| }; | ||||
| 
 | ||||
| /* Create multi string element */ | ||||
| /** Create multi string element */ | ||||
| WidgetElement* widget_element_string_multiline_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
| @ -38,7 +43,7 @@ WidgetElement* widget_element_string_multiline_create( | ||||
|     Font font, | ||||
|     const char* text); | ||||
| 
 | ||||
| /* Create string element */ | ||||
| /** Create string element */ | ||||
| WidgetElement* widget_element_string_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
| @ -47,17 +52,17 @@ WidgetElement* widget_element_string_create( | ||||
|     Font font, | ||||
|     const char* text); | ||||
| 
 | ||||
| /* Create button element */ | ||||
| /** Create button element */ | ||||
| WidgetElement* widget_element_button_create( | ||||
|     GuiButtonType button_type, | ||||
|     const char* text, | ||||
|     ButtonCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| /* Create icon element */ | ||||
| /** Create icon element */ | ||||
| WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon); | ||||
| 
 | ||||
| /* Create frame element */ | ||||
| /** Create frame element */ | ||||
| WidgetElement* widget_element_frame_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|  | ||||
| @ -1,14 +1,18 @@ | ||||
| /**
 | ||||
|  * @file scene_manager.h | ||||
|  * GUI: SceneManager API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| /** Scene Manager events type
 | ||||
|  */ | ||||
| /** Scene Manager events type */ | ||||
| typedef enum { | ||||
|     SceneManagerEventTypeCustom, | ||||
|     SceneManagerEventTypeBack, | ||||
| @ -44,6 +48,7 @@ typedef struct { | ||||
| typedef struct SceneManager SceneManager; | ||||
| 
 | ||||
| /** Set Scene state
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      scene_id       Scene ID | ||||
|  * @param      state          Scene new state | ||||
| @ -51,69 +56,90 @@ typedef struct SceneManager SceneManager; | ||||
| void scene_manager_set_scene_state(SceneManager* scene_manager, uint32_t scene_id, uint32_t state); | ||||
| 
 | ||||
| /** Get Scene state
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      scene_id       Scene ID | ||||
|  * | ||||
|  * @return     Scene state | ||||
|  */ | ||||
| uint32_t scene_manager_get_scene_state(SceneManager* scene_manager, uint32_t scene_id); | ||||
| 
 | ||||
| /** Scene Manager allocation and configuration
 | ||||
|  * | ||||
|  * Scene Manager allocates all scenes internally | ||||
|  * | ||||
|  * @param      app_scene_handlers  SceneManagerHandlers instance | ||||
|  * @param      context             context to be set on Scene handlers calls | ||||
|  * | ||||
|  * @return     SceneManager instance | ||||
|  */ | ||||
| SceneManager* scene_manager_alloc(const SceneManagerHandlers* app_scene_handlers, void* context); | ||||
| 
 | ||||
| /** Free Scene Manager with allocated Scenes
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  */ | ||||
| void scene_manager_free(SceneManager* scene_manager); | ||||
| 
 | ||||
| /** Custom event handler
 | ||||
|  * | ||||
|  * Calls Scene event handler with Custom event parameter | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      custom_event   Custom event code | ||||
|  * | ||||
|  * @return     true if event was consumed, false otherwise | ||||
|  */ | ||||
| bool scene_manager_handle_custom_event(SceneManager* scene_manager, uint32_t custom_event); | ||||
| 
 | ||||
| /** Back event handler
 | ||||
|  * | ||||
|  * Calls Scene event handler with Back event parameter | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * | ||||
|  * @return     true if event was consumed, false otherwise | ||||
|  */ | ||||
| bool scene_manager_handle_back_event(SceneManager* scene_manager); | ||||
| 
 | ||||
| /** Tick event handler
 | ||||
|  * | ||||
|  * Calls Scene event handler with Tick event parameter | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @return     true if event was consumed, false otherwise | ||||
|  */ | ||||
| void scene_manager_handle_tick_event(SceneManager* scene_manager); | ||||
| 
 | ||||
| /** Add and run next Scene
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      next_scene_id  next Scene ID | ||||
|  */ | ||||
| void scene_manager_next_scene(SceneManager* scene_manager, uint32_t next_scene_id); | ||||
| 
 | ||||
| /** Run previous Scene
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * | ||||
|  * @return     true if previous scene was found, false otherwise | ||||
|  */ | ||||
| bool scene_manager_previous_scene(SceneManager* scene_manager); | ||||
| 
 | ||||
| /** Search previous Scene
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      scene_id       Scene ID | ||||
|  * | ||||
|  * @return     true if previous scene was found, false otherwise | ||||
|  */ | ||||
| bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id); | ||||
| 
 | ||||
| /** Search and switch to previous Scene
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      scene_id       Scene ID | ||||
|  * | ||||
|  * @return     true if previous scene was found, false otherwise | ||||
|  */ | ||||
| bool scene_manager_search_and_switch_to_previous_scene( | ||||
| @ -121,8 +147,10 @@ bool scene_manager_search_and_switch_to_previous_scene( | ||||
|     uint32_t scene_id); | ||||
| 
 | ||||
| /** Clear Scene stack and switch to another Scene
 | ||||
|  * | ||||
|  * @param      scene_manager  SceneManager instance | ||||
|  * @param      scene_id       Scene ID | ||||
|  * | ||||
|  * @return     true if previous scene was found, false otherwise | ||||
|  */ | ||||
| bool scene_manager_search_and_switch_to_another_scene( | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file scene_manager_i.h | ||||
|  * GUI: internal SceneManager API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "scene_manager.h" | ||||
|  | ||||
| @ -12,6 +12,11 @@ void view_free(View* view) { | ||||
|     free(view); | ||||
| } | ||||
| 
 | ||||
| void view_tie_icon_animation(View* view, IconAnimation* icon_animation) { | ||||
|     furi_assert(view); | ||||
|     icon_animation_set_update_callback(icon_animation, view_icon_animation_callback, view); | ||||
| } | ||||
| 
 | ||||
| void view_set_draw_callback(View* view, ViewDrawCallback callback) { | ||||
|     furi_assert(view); | ||||
|     furi_assert(view->draw_callback == NULL); | ||||
| @ -120,6 +125,14 @@ void view_commit_model(View* view, bool update) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void view_icon_animation_callback(IconAnimation* instance, void* context) { | ||||
|     furi_assert(context); | ||||
|     View* view = context; | ||||
|     if(view->update_callback) { | ||||
|         view->update_callback(view, view->update_callback_context); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void view_unlock_model(View* view) { | ||||
|     furi_assert(view); | ||||
|     if(view->model_type == ViewModelTypeLocking) { | ||||
|  | ||||
| @ -1,6 +1,13 @@ | ||||
| /**
 | ||||
|  * @file view.h | ||||
|  * GUI: View API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <input/input.h> | ||||
| 
 | ||||
| #include "icon_animation.h" | ||||
| #include "canvas.h" | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| @ -10,9 +17,10 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Hides drawing view_port */ | ||||
| /** Hides drawing view_port */ | ||||
| #define VIEW_NONE 0xFFFFFFFF | ||||
| /* Ignore navigation event */ | ||||
| 
 | ||||
| /** Ignore navigation event */ | ||||
| #define VIEW_IGNORE 0xFFFFFFFE | ||||
| 
 | ||||
| typedef enum { | ||||
| @ -20,17 +28,17 @@ typedef enum { | ||||
|     ViewOrientationVertical, | ||||
| } ViewOrientation; | ||||
| 
 | ||||
| /* View, anonymous type */ | ||||
| /** View, anonymous type */ | ||||
| typedef struct View View; | ||||
| 
 | ||||
| /* View Draw callback
 | ||||
| /** View Draw callback
 | ||||
|  * @param      canvas,      pointer to canvas | ||||
|  * @param      view_model,  pointer to context | ||||
|  * @warning    called from GUI thread | ||||
|  */ | ||||
| typedef void (*ViewDrawCallback)(Canvas* canvas, void* model); | ||||
| 
 | ||||
| /* View Input callback
 | ||||
| /** View Input callback
 | ||||
|  * @param      event,    pointer to input event data | ||||
|  * @param      context,  pointer to context | ||||
|  * @return     true if event handled, false if event ignored | ||||
| @ -38,140 +46,163 @@ typedef void (*ViewDrawCallback)(Canvas* canvas, void* model); | ||||
|  */ | ||||
| typedef bool (*ViewInputCallback)(InputEvent* event, void* context); | ||||
| 
 | ||||
| /* View Custom callback
 | ||||
| /** View Custom callback
 | ||||
|  * @param      event,    number of custom event | ||||
|  * @param      context,  pointer to context | ||||
|  * @return     true if event handled, false if event ignored | ||||
|  */ | ||||
| typedef bool (*ViewCustomCallback)(uint32_t event, void* context); | ||||
| 
 | ||||
| /* View navigation callback
 | ||||
| /** View navigation callback
 | ||||
|  * @param      context,  pointer to context | ||||
|  * @return     next view id | ||||
|  * @warning    called from GUI thread | ||||
|  */ | ||||
| typedef uint32_t (*ViewNavigationCallback)(void* context); | ||||
| 
 | ||||
| /* View callback
 | ||||
| /** View callback
 | ||||
|  * @param      context,  pointer to context | ||||
|  * @warning    called from GUI thread | ||||
|  */ | ||||
| typedef void (*ViewCallback)(void* context); | ||||
| 
 | ||||
| /* View Update Callback
 | ||||
|  * Called upon model change, need to be propagated to GUI throw ViewPort update | ||||
| /** View Update Callback Called upon model change, need to be propagated to GUI
 | ||||
|  * throw ViewPort update | ||||
|  * @param      view,     pointer to view | ||||
|  * @param      context,  pointer to context | ||||
|  * @warning    called from GUI thread | ||||
|  */ | ||||
| typedef void (*ViewUpdateCallback)(View* view, void* context); | ||||
| 
 | ||||
| /* View model types */ | ||||
| /** View model types */ | ||||
| typedef enum { | ||||
|     /* Model is not allocated */ | ||||
|     /** Model is not allocated */ | ||||
|     ViewModelTypeNone, | ||||
|     /* Model consist of atomic types and/or partial update is not critical for rendering.
 | ||||
|     /** Model consist of atomic types and/or partial update is not critical for rendering.
 | ||||
|      * Lock free. | ||||
|      */ | ||||
|     ViewModelTypeLockFree, | ||||
|     /* Model access is guarded with mutex.
 | ||||
|     /** Model access is guarded with mutex.
 | ||||
|      * Locking gui thread. | ||||
|      */ | ||||
|     ViewModelTypeLocking, | ||||
| } ViewModelType; | ||||
| 
 | ||||
| /* Allocate and init View
 | ||||
|  * @return pointer to View | ||||
| /** Allocate and init View
 | ||||
|  * @return View instance | ||||
|  */ | ||||
| View* view_alloc(); | ||||
| 
 | ||||
| /* Free View
 | ||||
|  * @param pointer to View | ||||
| /** Free View
 | ||||
|  * | ||||
|  * @param      view  instance | ||||
|  */ | ||||
| void view_free(View* view); | ||||
| 
 | ||||
| /* Set View Draw callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, draw callback | ||||
| /** Tie IconAnimation with View
 | ||||
|  * | ||||
|  * @param      view            View instance | ||||
|  * @param      icon_animation  IconAnimation instance | ||||
|  */ | ||||
| void view_tie_icon_animation(View* view, IconAnimation* icon_animation); | ||||
| 
 | ||||
| /** Set View Draw callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  draw callback | ||||
|  */ | ||||
| void view_set_draw_callback(View* view, ViewDrawCallback callback); | ||||
| 
 | ||||
| /* Set View Input callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, input callback | ||||
| /** Set View Input callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  input callback | ||||
|  */ | ||||
| void view_set_input_callback(View* view, ViewInputCallback callback); | ||||
| 
 | ||||
| /* Set View Custom callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, input callback | ||||
| /** Set View Custom callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  input callback | ||||
|  */ | ||||
| void view_set_custom_callback(View* view, ViewCustomCallback callback); | ||||
| 
 | ||||
| /* Set Navigation Previous callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, input callback | ||||
| /** Set Navigation Previous callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  input callback | ||||
|  */ | ||||
| void view_set_previous_callback(View* view, ViewNavigationCallback callback); | ||||
| 
 | ||||
| /* Set Enter callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, callback | ||||
| /** Set Enter callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  callback | ||||
|  */ | ||||
| void view_set_enter_callback(View* view, ViewCallback callback); | ||||
| 
 | ||||
| /* Set Exit callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, callback | ||||
| /** Set Exit callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  callback | ||||
|  */ | ||||
| void view_set_exit_callback(View* view, ViewCallback callback); | ||||
| 
 | ||||
| /* Set Update callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param callback, callback | ||||
| /** Set Update callback
 | ||||
|  * | ||||
|  * @param      view      View instance | ||||
|  * @param      callback  callback | ||||
|  */ | ||||
| void view_set_update_callback(View* view, ViewUpdateCallback callback); | ||||
| 
 | ||||
| /* Set View Draw callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param context, context for callbacks | ||||
| /** Set View Draw callback
 | ||||
|  * | ||||
|  * @param      view     View instance | ||||
|  * @param      context  context for callbacks | ||||
|  */ | ||||
| void view_set_update_callback_context(View* view, void* context); | ||||
| 
 | ||||
| /* Set View Draw callback
 | ||||
|  * @param view, pointer to View | ||||
|  * @param context, context for callbacks | ||||
| /** Set View Draw callback
 | ||||
|  * | ||||
|  * @param      view     View instance | ||||
|  * @param      context  context for callbacks | ||||
|  */ | ||||
| void view_set_context(View* view, void* context); | ||||
| 
 | ||||
| /* Set View Orientation
 | ||||
|  * @param view, pointer to View | ||||
|  * @param orientation, either vertical or horizontal | ||||
| /** Set View Orientation
 | ||||
|  * | ||||
|  * @param      view         View instance | ||||
|  * @param      orientation  either vertical or horizontal | ||||
|  */ | ||||
| void view_set_orientation(View* view, ViewOrientation orientation); | ||||
| 
 | ||||
| /* Allocate view model.
 | ||||
|  * @param view, pointer to View | ||||
|  * @param type, View Model Type | ||||
|  * @param size, size | ||||
| /** Allocate view model.
 | ||||
|  * | ||||
|  * @param      view  View instance | ||||
|  * @param      type  View Model Type | ||||
|  * @param      size  size | ||||
|  */ | ||||
| void view_allocate_model(View* view, ViewModelType type, size_t size); | ||||
| 
 | ||||
| /* Free view model data memory.
 | ||||
|  * @param view, pointer to View | ||||
| /** Free view model data memory.
 | ||||
|  * | ||||
|  * @param      view  View instance | ||||
|  */ | ||||
| void view_free_model(View* view); | ||||
| 
 | ||||
| /* Get view model data
 | ||||
|  * @param view, pointer to View | ||||
| /** Get view model data
 | ||||
|  * | ||||
|  * @param      view  View instance | ||||
|  * | ||||
|  * @return     pointer to model data | ||||
|  * @warning    Don't forget to commit model changes | ||||
|  */ | ||||
| void* view_get_model(View* view); | ||||
| 
 | ||||
| /* Commit view model
 | ||||
|  * @param view, pointer to View | ||||
|  * @param update, true if you want to emit view update, false otherwise | ||||
| /** Commit view model
 | ||||
|  * | ||||
|  * @param      view    View instance | ||||
|  * @param      update  true if you want to emit view update, false otherwise | ||||
|  */ | ||||
| void view_commit_model(View* view, bool update); | ||||
| 
 | ||||
| @ -187,10 +218,12 @@ void view_commit_model(View* view, bool update); | ||||
|         view_commit_model(view, update);                    \ | ||||
|     } | ||||
| #else | ||||
| /* 
 | ||||
|  * With clause for view model | ||||
|  * @param view, View instance pointer | ||||
|  * @param function_body a (){} lambda declaration, executed within you parent function context | ||||
| /** With clause for view model
 | ||||
|  * | ||||
|  * @param      view           View instance pointer | ||||
|  * @param      function_body  a (){} lambda declaration, executed within you | ||||
|  *                            parent function context | ||||
|  * | ||||
|  * @return     true if you want to emit view update, false otherwise | ||||
|  */ | ||||
| #define with_view_model(view, function_body)                      \ | ||||
|  | ||||
| @ -177,6 +177,18 @@ void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t vi | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void view_dispatcher_send_to_front(ViewDispatcher* view_dispatcher) { | ||||
|     furi_assert(view_dispatcher); | ||||
|     furi_assert(view_dispatcher->gui); | ||||
|     gui_view_port_send_to_front(view_dispatcher->gui, view_dispatcher->view_port); | ||||
| } | ||||
| 
 | ||||
| void view_dispatcher_send_to_back(ViewDispatcher* view_dispatcher) { | ||||
|     furi_assert(view_dispatcher); | ||||
|     furi_assert(view_dispatcher->gui); | ||||
|     gui_view_port_send_to_front(view_dispatcher->gui, view_dispatcher->view_port); | ||||
| } | ||||
| 
 | ||||
| void view_dispatcher_attach_to_gui( | ||||
|     ViewDispatcher* view_dispatcher, | ||||
|     Gui* gui, | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file view_dispatcher.h | ||||
|  * GUI: ViewDispatcher API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "view.h" | ||||
| @ -8,8 +13,7 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /** ViewDispatcher view_port placement
 | ||||
|  */ | ||||
| /** ViewDispatcher view_port placement */ | ||||
| typedef enum { | ||||
|     ViewDispatcherTypeNone, /**< Special layer for internal use only */ | ||||
|     ViewDispatcherTypeWindow, /**< Main view_port layer, status bar is shown */ | ||||
| @ -18,41 +22,47 @@ typedef enum { | ||||
| 
 | ||||
| typedef struct ViewDispatcher ViewDispatcher; | ||||
| 
 | ||||
| /** Prototype for custom event callback
 | ||||
|  */ | ||||
| /** Prototype for custom event callback */ | ||||
| typedef bool (*ViewDispatcherCustomEventCallback)(void* context, uint32_t event); | ||||
| 
 | ||||
| /** Prototype for navigation event callback
 | ||||
|  */ | ||||
| /** Prototype for navigation event callback */ | ||||
| typedef bool (*ViewDispatcherNavigationEventCallback)(void* context); | ||||
| 
 | ||||
| /** Prototype for tick event callback
 | ||||
|  */ | ||||
| /** Prototype for tick event callback */ | ||||
| typedef void (*ViewDispatcherTickEventCallback)(void* context); | ||||
| 
 | ||||
| /** Allocate ViewDispatcher instance
 | ||||
|  * | ||||
|  * @return     pointer to ViewDispatcher instance | ||||
|  */ | ||||
| ViewDispatcher* view_dispatcher_alloc(); | ||||
| 
 | ||||
| /** Free ViewDispatcher instance
 | ||||
|  * | ||||
|  * @param      view_dispatcher  pointer to ViewDispatcher | ||||
|  */ | ||||
| void view_dispatcher_free(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /** Enable queue support
 | ||||
|  * If queue enabled all input and custom events will be dispatched throw internal queue | ||||
|  * | ||||
|  * If queue enabled all input and custom events will be dispatched throw | ||||
|  * internal queue | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  */ | ||||
| void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /** Send custom event
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param[in]  event            The event | ||||
|  */ | ||||
| void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event); | ||||
| 
 | ||||
| /** Set custom event handler
 | ||||
|  * | ||||
|  * Called on Custom Event, if it is not consumed by view | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      callback         ViewDispatcherCustomEventCallback instance | ||||
|  */ | ||||
| @ -61,7 +71,9 @@ void view_dispatcher_set_custom_event_callback( | ||||
|     ViewDispatcherCustomEventCallback callback); | ||||
| 
 | ||||
| /** Set navigation event handler
 | ||||
|  * | ||||
|  * Called on Input Short Back Event, if it is not consumed by view | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      callback         ViewDispatcherNavigationEventCallback instance | ||||
|  */ | ||||
| @ -70,6 +82,7 @@ void view_dispatcher_set_navigation_event_callback( | ||||
|     ViewDispatcherNavigationEventCallback callback); | ||||
| 
 | ||||
| /** Set tick event handler
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      callback         ViewDispatcherTickEventCallback | ||||
|  * @param      tick_period      callback call period | ||||
| @ -80,46 +93,69 @@ void view_dispatcher_set_tick_event_callback( | ||||
|     uint32_t tick_period); | ||||
| 
 | ||||
| /** Set event callback context
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      context          pointer to context | ||||
|  */ | ||||
| void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher, void* context); | ||||
| 
 | ||||
| /** Run ViewDispatcher
 | ||||
|  * | ||||
|  * Use only after queue enabled | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  */ | ||||
| void view_dispatcher_run(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /** Stop ViewDispatcher
 | ||||
|  * | ||||
|  * Use only after queue enabled | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  */ | ||||
| void view_dispatcher_stop(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /** Add view to ViewDispatcher
 | ||||
|  * @param view_dispatcher, ViewDispatcher instance | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      view_id          View id to register | ||||
|  * @param      view             View instance | ||||
|  */ | ||||
| void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view); | ||||
| 
 | ||||
| /** Remove view from ViewDispatcher
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      view_id          View id to remove | ||||
|  */ | ||||
| void view_dispatcher_remove_view(ViewDispatcher* view_dispatcher, uint32_t view_id); | ||||
| 
 | ||||
| /** Switch to View
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      view_id          View id to register | ||||
|  * @warning switching may be delayed till input events complementarity reached | ||||
|  * @warning    switching may be delayed till input events complementarity | ||||
|  *             reached | ||||
|  */ | ||||
| void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id); | ||||
| 
 | ||||
| /** Send ViewPort of this ViewDispatcher instance to front
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  */ | ||||
| void view_dispatcher_send_to_front(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /** Send ViewPort of this ViewDispatcher instance to back
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  */ | ||||
| void view_dispatcher_send_to_back(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /** Attach ViewDispatcher to GUI
 | ||||
|  * | ||||
|  * @param      view_dispatcher  ViewDispatcher instance | ||||
|  * @param      gui              GUI instance to attach to | ||||
|  * @param[in]  type             The type | ||||
|  */ | ||||
| void view_dispatcher_attach_to_gui( | ||||
|     ViewDispatcher* view_dispatcher, | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| /**
 | ||||
|  * @file view_dispatcher_i.h | ||||
|  * GUI: ViewDispatcher API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi.h> | ||||
| @ -41,23 +46,23 @@ typedef struct { | ||||
|     }; | ||||
| } ViewDispatcherMessage; | ||||
| 
 | ||||
| /* ViewPort Draw Callback */ | ||||
| /** ViewPort Draw Callback */ | ||||
| void view_dispatcher_draw_callback(Canvas* canvas, void* context); | ||||
| 
 | ||||
| /* ViewPort Input Callback */ | ||||
| /** ViewPort Input Callback */ | ||||
| void view_dispatcher_input_callback(InputEvent* event, void* context); | ||||
| 
 | ||||
| /* Input handler */ | ||||
| /** Input handler */ | ||||
| void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event); | ||||
| 
 | ||||
| /* Tick handler */ | ||||
| /** Tick handler */ | ||||
| void view_dispatcher_handle_tick_event(ViewDispatcher* view_dispatcher); | ||||
| 
 | ||||
| /* Custom event handler */ | ||||
| /** Custom event handler */ | ||||
| void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32_t event); | ||||
| 
 | ||||
| /* Set current view, dispatches view enter and exit */ | ||||
| /** Set current view, dispatches view enter and exit */ | ||||
| void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view); | ||||
| 
 | ||||
| /* ViewDispatcher update event */ | ||||
| /** ViewDispatcher update event */ | ||||
| void view_dispatcher_update(View* view, void* context); | ||||
|  | ||||
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