Merge remote-tracking branch 'origin/release-candidate' into release
| @ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| Thank you for investing your time in contributing to our project!  | 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. | Read our [Code of Conduct](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. | In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR. | ||||||
| 
 | 
 | ||||||
| @ -17,12 +17,12 @@ See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some hel | |||||||
| 
 | 
 | ||||||
| ## Getting started | ## Getting started | ||||||
| 
 | 
 | ||||||
| Before writing code and creating PR make sure that it aligns with our mission and guidlines: | Before writing code and creating PR make sure that it aligns with our mission and guidelines: | ||||||
| 
 | 
 | ||||||
| - All our devices are intended for research and education. | - All our devices are intended for research and education. | ||||||
| - PR that contains code intended to commit crimes is not going to be accepted. | - PR that contains code intended to commit crimes is not going to be accepted. | ||||||
| - Your PR must comply with our [Coding Style](CODING_STYLE.md) | - Your PR must comply with our [Coding Style](CODING_STYLE.md) | ||||||
| - Your PR must contain code compatiable with project [LICENSE](LICENSE). | - Your PR must contain code compatible with project [LICENSE](LICENSE). | ||||||
| - PR will only be merged if it pass CI/CD. | - PR will only be merged if it pass CI/CD. | ||||||
| - PR will only be merged if it pass review by code owner. | - PR will only be merged if it pass review by code owner. | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -71,7 +71,7 @@ void WIEGAND::end() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void WIEGAND::ReadD0() { | void WIEGAND::ReadD0() { | ||||||
|     _bitCount++; // Increament bit count for Interrupt connected to D0
 |     _bitCount++; // Increment bit count for Interrupt connected to D0
 | ||||||
|     if(_bitCount > 31) // If bit count more than 31, process high bits
 |     if(_bitCount > 31) // If bit count more than 31, process high bits
 | ||||||
|     { |     { | ||||||
|         _cardTempHigh |= ((0x80000000 & _cardTemp) >> 31); //	shift value to high bits
 |         _cardTempHigh |= ((0x80000000 & _cardTemp) >> 31); //	shift value to high bits
 | ||||||
|  | |||||||
| @ -98,7 +98,7 @@ void bt_debug_app_free(BtDebugApp* app) { | |||||||
| int32_t bt_debug_app(void* p) { | int32_t bt_debug_app(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|     if(!furi_hal_bt_is_testing_supported()) { |     if(!furi_hal_bt_is_testing_supported()) { | ||||||
|         FURI_LOG_E(TAG, "Incorrect radio stack: radio testing fetures are absent."); |         FURI_LOG_E(TAG, "Incorrect radio stack: radio testing features are absent."); | ||||||
|         DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); |         DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|         dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); |         dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); | ||||||
|         return 255; |         return 255; | ||||||
|  | |||||||
| @ -145,7 +145,7 @@ DisplayTest* display_test_alloc() { | |||||||
|     view_set_previous_callback(view, display_test_previous_callback); |     view_set_previous_callback(view, display_test_previous_callback); | ||||||
|     view_dispatcher_add_view(instance->view_dispatcher, DisplayTestViewConfigure, view); |     view_dispatcher_add_view(instance->view_dispatcher, DisplayTestViewConfigure, view); | ||||||
| 
 | 
 | ||||||
|     // Configurtion items
 |     // Configuration items
 | ||||||
|     VariableItem* item; |     VariableItem* item; | ||||||
|     instance->config_bias = false; |     instance->config_bias = false; | ||||||
|     instance->config_contrast = 32; |     instance->config_contrast = 32; | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								applications/debug/rpc_debug_app/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | |||||||
|  | App( | ||||||
|  |     appid="rpc_debug", | ||||||
|  |     name="RPC Debug", | ||||||
|  |     apptype=FlipperAppType.DEBUG, | ||||||
|  |     entry_point="rpc_debug_app", | ||||||
|  |     requires=["gui", "rpc_start", "notification"], | ||||||
|  |     stack_size=2 * 1024, | ||||||
|  |     order=10, | ||||||
|  |     fap_category="Debug", | ||||||
|  | ) | ||||||
							
								
								
									
										138
									
								
								applications/debug/rpc_debug_app/rpc_debug_app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,138 @@ | |||||||
|  | #include "rpc_debug_app.h" | ||||||
|  | #include <core/log.h> | ||||||
|  | 
 | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | static bool rpc_debug_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool rpc_debug_app_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     return scene_manager_handle_back_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_tick_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     scene_manager_handle_tick_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     furi_assert(app->rpc); | ||||||
|  | 
 | ||||||
|  |     if(event == RpcAppEventSessionClose) { | ||||||
|  |         scene_manager_stop(app->scene_manager); | ||||||
|  |         view_dispatcher_stop(app->view_dispatcher); | ||||||
|  |         rpc_system_app_set_callback(app->rpc, NULL, NULL); | ||||||
|  |         app->rpc = NULL; | ||||||
|  |     } else if(event == RpcAppEventAppExit) { | ||||||
|  |         scene_manager_stop(app->scene_manager); | ||||||
|  |         view_dispatcher_stop(app->view_dispatcher); | ||||||
|  |         rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true); | ||||||
|  |     } else { | ||||||
|  |         rpc_system_app_confirm(app->rpc, event, false); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool rpc_debug_app_rpc_init_rpc(RpcDebugApp* app, const char* args) { | ||||||
|  |     bool ret = false; | ||||||
|  |     if(args && strlen(args)) { | ||||||
|  |         uint32_t rpc = 0; | ||||||
|  |         if(sscanf(args, "RPC %lX", &rpc) == 1) { | ||||||
|  |             app->rpc = (RpcAppSystem*)rpc; | ||||||
|  |             rpc_system_app_set_callback(app->rpc, rpc_debug_app_rpc_command_callback, app); | ||||||
|  |             rpc_system_app_send_started(app->rpc); | ||||||
|  |             ret = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static RpcDebugApp* rpc_debug_app_alloc() { | ||||||
|  |     RpcDebugApp* app = malloc(sizeof(RpcDebugApp)); | ||||||
|  | 
 | ||||||
|  |     app->gui = furi_record_open(RECORD_GUI); | ||||||
|  |     app->notifications = furi_record_open(RECORD_NOTIFICATION); | ||||||
|  |     app->scene_manager = scene_manager_alloc(&rpc_debug_app_scene_handlers, app); | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         app->view_dispatcher, rpc_debug_app_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         app->view_dispatcher, rpc_debug_app_back_event_callback); | ||||||
|  |     view_dispatcher_set_tick_event_callback( | ||||||
|  |         app->view_dispatcher, rpc_debug_app_tick_event_callback, 100); | ||||||
|  |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|  |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     app->widget = widget_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, RpcDebugAppViewWidget, widget_get_view(app->widget)); | ||||||
|  |     app->submenu = submenu_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, RpcDebugAppViewSubmenu, submenu_get_view(app->submenu)); | ||||||
|  |     app->text_box = text_box_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, RpcDebugAppViewTextBox, text_box_get_view(app->text_box)); | ||||||
|  |     app->text_input = text_input_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, RpcDebugAppViewTextInput, text_input_get_view(app->text_input)); | ||||||
|  |     app->byte_input = byte_input_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, RpcDebugAppViewByteInput, byte_input_get_view(app->byte_input)); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_free(RpcDebugApp* app) { | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewByteInput); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewTextInput); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewTextBox); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewSubmenu); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewWidget); | ||||||
|  | 
 | ||||||
|  |     free(app->byte_input); | ||||||
|  |     free(app->text_input); | ||||||
|  |     free(app->text_box); | ||||||
|  |     free(app->submenu); | ||||||
|  |     free(app->widget); | ||||||
|  | 
 | ||||||
|  |     free(app->scene_manager); | ||||||
|  |     free(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     furi_record_close(RECORD_NOTIFICATION); | ||||||
|  |     app->notifications = NULL; | ||||||
|  |     furi_record_close(RECORD_GUI); | ||||||
|  |     app->gui = NULL; | ||||||
|  | 
 | ||||||
|  |     if(app->rpc) { | ||||||
|  |         rpc_system_app_set_callback(app->rpc, NULL, NULL); | ||||||
|  |         rpc_system_app_send_exited(app->rpc); | ||||||
|  |         app->rpc = NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t rpc_debug_app(void* args) { | ||||||
|  |     RpcDebugApp* app = rpc_debug_app_alloc(); | ||||||
|  | 
 | ||||||
|  |     if(rpc_debug_app_rpc_init_rpc(app, args)) { | ||||||
|  |         notification_message(app->notifications, &sequence_display_backlight_on); | ||||||
|  |         scene_manager_next_scene(app->scene_manager, RpcDebugAppSceneStart); | ||||||
|  |     } else { | ||||||
|  |         scene_manager_next_scene(app->scene_manager, RpcDebugAppSceneStartDummy); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     rpc_debug_app_free(app); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								applications/debug/rpc_debug_app/rpc_debug_app.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,54 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | 
 | ||||||
|  | #include <gui/modules/widget.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/text_box.h> | ||||||
|  | #include <gui/modules/text_input.h> | ||||||
|  | #include <gui/modules/byte_input.h> | ||||||
|  | 
 | ||||||
|  | #include <rpc/rpc_app.h> | ||||||
|  | #include <notification/notification_messages.h> | ||||||
|  | 
 | ||||||
|  | #include "scenes/rpc_debug_app_scene.h" | ||||||
|  | 
 | ||||||
|  | #define DATA_STORE_SIZE 64U | ||||||
|  | #define TEXT_STORE_SIZE 64U | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     Gui* gui; | ||||||
|  |     RpcAppSystem* rpc; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     NotificationApp* notifications; | ||||||
|  | 
 | ||||||
|  |     Widget* widget; | ||||||
|  |     Submenu* submenu; | ||||||
|  |     TextBox* text_box; | ||||||
|  |     TextInput* text_input; | ||||||
|  |     ByteInput* byte_input; | ||||||
|  | 
 | ||||||
|  |     char text_store[TEXT_STORE_SIZE]; | ||||||
|  |     uint8_t data_store[DATA_STORE_SIZE]; | ||||||
|  | } RpcDebugApp; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     RpcDebugAppViewWidget, | ||||||
|  |     RpcDebugAppViewSubmenu, | ||||||
|  |     RpcDebugAppViewTextBox, | ||||||
|  |     RpcDebugAppViewTextInput, | ||||||
|  |     RpcDebugAppViewByteInput, | ||||||
|  | } RpcDebugAppView; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     // Reserve first 100 events for button types and indexes, starting from 0
 | ||||||
|  |     RpcDebugAppCustomEventInputErrorCode = 100, | ||||||
|  |     RpcDebugAppCustomEventInputErrorText, | ||||||
|  |     RpcDebugAppCustomEventInputDataExchange, | ||||||
|  |     RpcDebugAppCustomEventRpcDataExchange, | ||||||
|  | } RpcDebugAppCustomEvent; | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "rpc_debug_app_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const rpc_debug_app_on_enter_handlers[])(void*) = { | ||||||
|  | #include "rpc_debug_app_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 rpc_debug_app_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "rpc_debug_app_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 rpc_debug_app_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "rpc_debug_app_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers rpc_debug_app_scene_handlers = { | ||||||
|  |     .on_enter_handlers = rpc_debug_app_on_enter_handlers, | ||||||
|  |     .on_event_handlers = rpc_debug_app_on_event_handlers, | ||||||
|  |     .on_exit_handlers = rpc_debug_app_on_exit_handlers, | ||||||
|  |     .scene_num = RpcDebugAppSceneNum, | ||||||
|  | }; | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) RpcDebugAppScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "rpc_debug_app_scene_config.h" | ||||||
|  |     RpcDebugAppSceneNum, | ||||||
|  | } RpcDebugAppScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers rpc_debug_app_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "rpc_debug_app_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 "rpc_debug_app_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 "rpc_debug_app_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | ADD_SCENE(rpc_debug_app, start, Start) | ||||||
|  | ADD_SCENE(rpc_debug_app, start_dummy, StartDummy) | ||||||
|  | ADD_SCENE(rpc_debug_app, test_app_error, TestAppError) | ||||||
|  | ADD_SCENE(rpc_debug_app, test_data_exchange, TestDataExchange) | ||||||
|  | ADD_SCENE(rpc_debug_app, input_error_code, InputErrorCode) | ||||||
|  | ADD_SCENE(rpc_debug_app, input_error_text, InputErrorText) | ||||||
|  | ADD_SCENE(rpc_debug_app, input_data_exchange, InputDataExchange) | ||||||
|  | ADD_SCENE(rpc_debug_app, receive_data_exchange, ReceiveDataExchange) | ||||||
| @ -0,0 +1,40 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_input_data_exchange_result_callback(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event( | ||||||
|  |         app->view_dispatcher, RpcDebugAppCustomEventInputDataExchange); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_input_data_exchange_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     byte_input_set_header_text(app->byte_input, "Enter data to exchange"); | ||||||
|  |     byte_input_set_result_callback( | ||||||
|  |         app->byte_input, | ||||||
|  |         rpc_debug_app_scene_input_data_exchange_result_callback, | ||||||
|  |         NULL, | ||||||
|  |         app, | ||||||
|  |         app->data_store, | ||||||
|  |         DATA_STORE_SIZE); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewByteInput); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_input_data_exchange_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == RpcDebugAppCustomEventInputDataExchange) { | ||||||
|  |             rpc_system_app_exchange_data(app->rpc, app->data_store, DATA_STORE_SIZE); | ||||||
|  |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_input_data_exchange_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     UNUSED(app); | ||||||
|  | } | ||||||
| @ -0,0 +1,60 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | static bool rpc_debug_app_scene_input_error_code_validator_callback( | ||||||
|  |     const char* text, | ||||||
|  |     FuriString* error, | ||||||
|  |     void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  | 
 | ||||||
|  |     for(; *text; ++text) { | ||||||
|  |         const char c = *text; | ||||||
|  |         if(c < '0' || c > '9') { | ||||||
|  |             furi_string_printf(error, "%s", "Please enter\na number!"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_input_error_code_result_callback(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventInputErrorCode); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_input_error_code_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     strncpy(app->text_store, "666", TEXT_STORE_SIZE); | ||||||
|  |     text_input_set_header_text(app->text_input, "Enter error code"); | ||||||
|  |     text_input_set_validator( | ||||||
|  |         app->text_input, rpc_debug_app_scene_input_error_code_validator_callback, NULL); | ||||||
|  |     text_input_set_result_callback( | ||||||
|  |         app->text_input, | ||||||
|  |         rpc_debug_app_scene_input_error_code_result_callback, | ||||||
|  |         app, | ||||||
|  |         app->text_store, | ||||||
|  |         TEXT_STORE_SIZE, | ||||||
|  |         true); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextInput); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == RpcDebugAppCustomEventInputErrorCode) { | ||||||
|  |             rpc_system_app_set_error_code(app->rpc, (uint32_t)atol(app->text_store)); | ||||||
|  |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_input_error_code_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     text_input_reset(app->text_input); | ||||||
|  |     text_input_set_validator(app->text_input, NULL, NULL); | ||||||
|  | } | ||||||
| @ -0,0 +1,40 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_input_error_text_result_callback(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventInputErrorText); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_input_error_text_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     strncpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE); | ||||||
|  |     text_input_set_header_text(app->text_input, "Enter error text"); | ||||||
|  |     text_input_set_result_callback( | ||||||
|  |         app->text_input, | ||||||
|  |         rpc_debug_app_scene_input_error_text_result_callback, | ||||||
|  |         app, | ||||||
|  |         app->text_store, | ||||||
|  |         TEXT_STORE_SIZE, | ||||||
|  |         true); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextInput); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_input_error_text_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == RpcDebugAppCustomEventInputErrorText) { | ||||||
|  |             rpc_system_app_set_error_text(app->rpc, app->text_store); | ||||||
|  |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_input_error_text_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     text_input_reset(app->text_input); | ||||||
|  | } | ||||||
| @ -0,0 +1,70 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_start_format_hex( | ||||||
|  |     const uint8_t* data, | ||||||
|  |     size_t data_size, | ||||||
|  |     char* buf, | ||||||
|  |     size_t buf_size) { | ||||||
|  |     furi_assert(data); | ||||||
|  |     furi_assert(buf); | ||||||
|  | 
 | ||||||
|  |     const size_t byte_width = 3; | ||||||
|  |     const size_t line_width = 7; | ||||||
|  | 
 | ||||||
|  |     data_size = MIN(data_size, buf_size / (byte_width + 1)); | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < data_size; ++i) { | ||||||
|  |         char* p = buf + (i * byte_width); | ||||||
|  |         char sep = !((i + 1) % line_width) ? '\n' : ' '; | ||||||
|  |         snprintf(p, byte_width + 1, "%02X%c", data[i], sep); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     buf[buf_size - 1] = '\0'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_receive_data_exchange_callback( | ||||||
|  |     const uint8_t* data, | ||||||
|  |     size_t data_size, | ||||||
|  |     void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     if(data) { | ||||||
|  |         rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE); | ||||||
|  |     } else { | ||||||
|  |         strncpy(app->text_store, "<Data empty>", TEXT_STORE_SIZE); | ||||||
|  |     } | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); | ||||||
|  | 
 | ||||||
|  |     text_box_set_text(app->text_box, app->text_store); | ||||||
|  |     text_box_set_font(app->text_box, TextBoxFontHex); | ||||||
|  | 
 | ||||||
|  |     rpc_system_app_set_data_exchange_callback( | ||||||
|  |         app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == RpcDebugAppCustomEventRpcDataExchange) { | ||||||
|  |             notification_message(app->notifications, &sequence_blink_cyan_100); | ||||||
|  |             notification_message(app->notifications, &sequence_display_backlight_on); | ||||||
|  |             text_box_set_text(app->text_box, app->text_store); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     text_box_reset(app->text_box); | ||||||
|  |     rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL); | ||||||
|  | } | ||||||
| @ -0,0 +1,57 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | enum SubmenuIndex { | ||||||
|  |     SubmenuIndexTestAppError, | ||||||
|  |     SubmenuIndexTestDataExchange, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_start_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_start_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Test App Error", | ||||||
|  |         SubmenuIndexTestAppError, | ||||||
|  |         rpc_debug_app_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Test Data Exchange", | ||||||
|  |         SubmenuIndexTestDataExchange, | ||||||
|  |         rpc_debug_app_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     submenu_set_selected_item(submenu, SubmenuIndexTestAppError); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     SceneManager* scene_manager = app->scene_manager; | ||||||
|  | 
 | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         const uint32_t submenu_index = event.event; | ||||||
|  |         if(submenu_index == SubmenuIndexTestAppError) { | ||||||
|  |             scene_manager_next_scene(scene_manager, RpcDebugAppSceneTestAppError); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(submenu_index == SubmenuIndexTestDataExchange) { | ||||||
|  |             scene_manager_next_scene(scene_manager, RpcDebugAppSceneTestDataExchange); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_start_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     submenu_reset(app->submenu); | ||||||
|  | } | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_start_dummy_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     widget_add_text_box_element( | ||||||
|  |         app->widget, | ||||||
|  |         0, | ||||||
|  |         0, | ||||||
|  |         128, | ||||||
|  |         64, | ||||||
|  |         AlignCenter, | ||||||
|  |         AlignCenter, | ||||||
|  |         "This application\nis meant to be run\nin \e#RPC\e# mode.", | ||||||
|  |         false); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_start_dummy_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     UNUSED(app); | ||||||
|  |     UNUSED(event); | ||||||
|  | 
 | ||||||
|  |     bool consumed = false; | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_start_dummy_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     widget_reset(app->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,57 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SubmenuIndexSetErrorCode, | ||||||
|  |     SubmenuIndexSetErrorText, | ||||||
|  | } SubmenuIndex; | ||||||
|  | 
 | ||||||
|  | static void rpc_debug_app_scene_test_app_error_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_test_app_error_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Set Error Code", | ||||||
|  |         SubmenuIndexSetErrorCode, | ||||||
|  |         rpc_debug_app_scene_test_app_error_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Set Error Text", | ||||||
|  |         SubmenuIndexSetErrorText, | ||||||
|  |         rpc_debug_app_scene_test_app_error_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     submenu_set_selected_item(submenu, SubmenuIndexSetErrorCode); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_test_app_error_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     SceneManager* scene_manager = app->scene_manager; | ||||||
|  | 
 | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         const uint32_t submenu_index = event.event; | ||||||
|  |         if(submenu_index == SubmenuIndexSetErrorCode) { | ||||||
|  |             scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputErrorCode); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(submenu_index == SubmenuIndexSetErrorText) { | ||||||
|  |             scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputErrorText); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_test_app_error_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     submenu_reset(app->submenu); | ||||||
|  | } | ||||||
| @ -0,0 +1,58 @@ | |||||||
|  | #include "../rpc_debug_app.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SubmenuIndexSendData, | ||||||
|  |     SubmenuIndexReceiveData, | ||||||
|  | } SubmenuIndex; | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     rpc_debug_app_scene_test_data_exchange_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_test_data_exchange_on_enter(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Send Data", | ||||||
|  |         SubmenuIndexSendData, | ||||||
|  |         rpc_debug_app_scene_test_data_exchange_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Receive Data", | ||||||
|  |         SubmenuIndexReceiveData, | ||||||
|  |         rpc_debug_app_scene_test_data_exchange_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     submenu_set_selected_item(submenu, SubmenuIndexSendData); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool rpc_debug_app_scene_test_data_exchange_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     SceneManager* scene_manager = app->scene_manager; | ||||||
|  | 
 | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         const uint32_t submenu_index = event.event; | ||||||
|  |         if(submenu_index == SubmenuIndexSendData) { | ||||||
|  |             scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputDataExchange); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(submenu_index == SubmenuIndexReceiveData) { | ||||||
|  |             scene_manager_next_scene(scene_manager, RpcDebugAppSceneReceiveDataExchange); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void rpc_debug_app_scene_test_data_exchange_on_exit(void* context) { | ||||||
|  |     RpcDebugApp* app = context; | ||||||
|  |     submenu_reset(app->submenu); | ||||||
|  | } | ||||||
| @ -220,11 +220,7 @@ static UartEchoApp* uart_echo_app_alloc() { | |||||||
|     furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); |     furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); | ||||||
|     furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); |     furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); | ||||||
| 
 | 
 | ||||||
|     app->worker_thread = furi_thread_alloc(); |     app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); | ||||||
|     furi_thread_set_name(app->worker_thread, "UsbUartWorker"); |  | ||||||
|     furi_thread_set_stack_size(app->worker_thread, 1024); |  | ||||||
|     furi_thread_set_context(app->worker_thread, app); |  | ||||||
|     furi_thread_set_callback(app->worker_thread, uart_echo_worker); |  | ||||||
|     furi_thread_start(app->worker_thread); |     furi_thread_start(app->worker_thread); | ||||||
| 
 | 
 | ||||||
|     return app; |     return app; | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ | |||||||
| extern size_t memmgr_get_free_heap(void); | extern size_t memmgr_get_free_heap(void); | ||||||
| extern size_t memmgr_get_minimum_free_heap(void); | extern size_t memmgr_get_minimum_free_heap(void); | ||||||
| 
 | 
 | ||||||
| // current heap managment realization consume:
 | // current heap management realization consume:
 | ||||||
| // X bytes after allocate and 0 bytes after allocate and free,
 | // X bytes after allocate and 0 bytes after allocate and free,
 | ||||||
| // where X = sizeof(void*) + sizeof(size_t), look to BlockLink_t
 | // where X = sizeof(void*) + sizeof(size_t), look to BlockLink_t
 | ||||||
| const size_t heap_overhead_max_size = sizeof(void*) + sizeof(size_t); | const size_t heap_overhead_max_size = sizeof(void*) + sizeof(size_t); | ||||||
| @ -21,7 +21,7 @@ bool heap_equal(size_t heap_size, size_t heap_size_old) { | |||||||
|     const size_t heap_low = heap_size_old - heap_overhead_max_size; |     const size_t heap_low = heap_size_old - heap_overhead_max_size; | ||||||
|     const size_t heap_high = heap_size_old + heap_overhead_max_size; |     const size_t heap_high = heap_size_old + heap_overhead_max_size; | ||||||
| 
 | 
 | ||||||
|     // not extact, so we must test it against bigger numbers than "overhead size"
 |     // not exact, so we must test it against bigger numbers than "overhead size"
 | ||||||
|     const bool result = ((heap_size >= heap_low) && (heap_size <= heap_high)); |     const bool result = ((heap_size >= heap_low) && (heap_size <= heap_high)); | ||||||
| 
 | 
 | ||||||
|     // debug allocation info
 |     // debug allocation info
 | ||||||
|  | |||||||
| @ -136,7 +136,7 @@ static bool nfc_test_digital_signal_test_encode( | |||||||
|             ref_timings_sum += ref[i]; |             ref_timings_sum += ref[i]; | ||||||
|             if(timings_diff > timing_tolerance) { |             if(timings_diff > timing_tolerance) { | ||||||
|                 FURI_LOG_E( |                 FURI_LOG_E( | ||||||
|                     TAG, "Too big differece in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); |                     TAG, "Too big difference in %d timings. Ref: %ld, DUT: %ld", i, ref[i], dut[i]); | ||||||
|                 timing_check_success = false; |                 timing_check_success = false; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -43,11 +43,8 @@ MU_TEST(storage_file_open_lock) { | |||||||
|     File* file = storage_file_alloc(storage); |     File* file = storage_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|     // file_locker thread start
 |     // file_locker thread start
 | ||||||
|     FuriThread* locker_thread = furi_thread_alloc(); |     FuriThread* locker_thread = | ||||||
|     furi_thread_set_name(locker_thread, "StorageFileLocker"); |         furi_thread_alloc_ex("StorageFileLocker", 2048, storage_file_locker, semaphore); | ||||||
|     furi_thread_set_stack_size(locker_thread, 2048); |  | ||||||
|     furi_thread_set_context(locker_thread, semaphore); |  | ||||||
|     furi_thread_set_callback(locker_thread, storage_file_locker); |  | ||||||
|     furi_thread_start(locker_thread); |     furi_thread_start(locker_thread); | ||||||
| 
 | 
 | ||||||
|     // wait for file lock
 |     // wait for file lock
 | ||||||
| @ -133,11 +130,8 @@ MU_TEST(storage_dir_open_lock) { | |||||||
|     File* file = storage_file_alloc(storage); |     File* file = storage_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|     // file_locker thread start
 |     // file_locker thread start
 | ||||||
|     FuriThread* locker_thread = furi_thread_alloc(); |     FuriThread* locker_thread = | ||||||
|     furi_thread_set_name(locker_thread, "StorageDirLocker"); |         furi_thread_alloc_ex("StorageDirLocker", 2048, storage_dir_locker, semaphore); | ||||||
|     furi_thread_set_stack_size(locker_thread, 2048); |  | ||||||
|     furi_thread_set_context(locker_thread, semaphore); |  | ||||||
|     furi_thread_set_callback(locker_thread, storage_dir_locker); |  | ||||||
|     furi_thread_start(locker_thread); |     furi_thread_start(locker_thread); | ||||||
| 
 | 
 | ||||||
|     // wait for dir lock
 |     // wait for dir lock
 | ||||||
|  | |||||||
| @ -219,21 +219,21 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { | |||||||
|     mu_check(stream_eof(stream)); |     mu_check(stream_eof(stream)); | ||||||
|     mu_assert_int_eq(0, stream_tell(stream)); |     mu_assert_int_eq(0, stream_tell(stream)); | ||||||
| 
 | 
 | ||||||
|     // insert formated string at the end of stream
 |     // insert formatted string at the end of stream
 | ||||||
|     // "" -> "dio666"
 |     // "" -> "dio666"
 | ||||||
|     mu_check(stream_insert_format(stream, "%s%d", "dio", 666)); |     mu_check(stream_insert_format(stream, "%s%d", "dio", 666)); | ||||||
|     mu_assert_int_eq(6, stream_size(stream)); |     mu_assert_int_eq(6, stream_size(stream)); | ||||||
|     mu_check(stream_eof(stream)); |     mu_check(stream_eof(stream)); | ||||||
|     mu_assert_int_eq(6, stream_tell(stream)); |     mu_assert_int_eq(6, stream_tell(stream)); | ||||||
| 
 | 
 | ||||||
|     // insert formated string at the end of stream
 |     // insert formatted string at the end of stream
 | ||||||
|     // "dio666" -> "dio666zlo555"
 |     // "dio666" -> "dio666zlo555"
 | ||||||
|     mu_check(stream_insert_format(stream, "%s%d", "zlo", 555)); |     mu_check(stream_insert_format(stream, "%s%d", "zlo", 555)); | ||||||
|     mu_assert_int_eq(12, stream_size(stream)); |     mu_assert_int_eq(12, stream_size(stream)); | ||||||
|     mu_check(stream_eof(stream)); |     mu_check(stream_eof(stream)); | ||||||
|     mu_assert_int_eq(12, stream_tell(stream)); |     mu_assert_int_eq(12, stream_tell(stream)); | ||||||
| 
 | 
 | ||||||
|     // insert formated string at the 6 pos
 |     // insert formatted string at the 6 pos
 | ||||||
|     // "dio666" -> "dio666baba13zlo555"
 |     // "dio666" -> "dio666baba13zlo555"
 | ||||||
|     mu_check(stream_seek(stream, 6, StreamOffsetFromStart)); |     mu_check(stream_seek(stream, 6, StreamOffsetFromStart)); | ||||||
|     mu_check(stream_insert_format(stream, "%s%d", "baba", 13)); |     mu_check(stream_insert_format(stream, "%s%d", "baba", 13)); | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
| #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") | #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") | ||||||
| #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") | #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") | ||||||
| #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") | #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") | ||||||
| #define TEST_RANDOM_COUNT_PARSE 232 | #define TEST_RANDOM_COUNT_PARSE 244 | ||||||
| #define TEST_TIMEOUT 10000 | #define TEST_TIMEOUT 10000 | ||||||
| 
 | 
 | ||||||
| static SubGhzEnvironment* environment_handler; | static SubGhzEnvironment* environment_handler; | ||||||
| @ -209,6 +209,149 @@ MU_TEST(subghz_keystore_test) { | |||||||
|         "Test keystore error"); |         "Test keystore error"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SubGhzHalAsyncTxTestTypeNormal, | ||||||
|  |     SubGhzHalAsyncTxTestTypeInvalidStart, | ||||||
|  |     SubGhzHalAsyncTxTestTypeInvalidMid, | ||||||
|  |     SubGhzHalAsyncTxTestTypeInvalidEnd, | ||||||
|  |     SubGhzHalAsyncTxTestTypeResetStart, | ||||||
|  |     SubGhzHalAsyncTxTestTypeResetMid, | ||||||
|  |     SubGhzHalAsyncTxTestTypeResetEnd, | ||||||
|  | } SubGhzHalAsyncTxTestType; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     SubGhzHalAsyncTxTestType type; | ||||||
|  |     size_t pos; | ||||||
|  | } SubGhzHalAsyncTxTest; | ||||||
|  | 
 | ||||||
|  | #define SUBGHZ_HAL_TEST_DURATION 1 | ||||||
|  | 
 | ||||||
|  | static LevelDuration subghz_hal_async_tx_test_yield(void* context) { | ||||||
|  |     SubGhzHalAsyncTxTest* test = context; | ||||||
|  |     bool is_odd = test->pos % 2; | ||||||
|  | 
 | ||||||
|  |     if(test->type == SubGhzHalAsyncTxTestTypeNormal) { | ||||||
|  |         if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidStart) { | ||||||
|  |         if(test->pos == 0) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) { | ||||||
|  |         if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) { | ||||||
|  |         if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else if(test->type == SubGhzHalAsyncTxTestTypeResetStart) { | ||||||
|  |         if(test->pos == 0) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) { | ||||||
|  |         if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) { | ||||||
|  |         if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||||
|  |         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||||
|  |             test->pos++; | ||||||
|  |             return level_duration_reset(); | ||||||
|  |         } else { | ||||||
|  |             furi_crash("Yield after reset"); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         furi_crash("Programming error"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { | ||||||
|  |     SubGhzHalAsyncTxTest test = {0}; | ||||||
|  |     test.type = type; | ||||||
|  |     furi_hal_subghz_reset(); | ||||||
|  |     furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); | ||||||
|  |     furi_hal_subghz_set_frequency_and_path(433920000); | ||||||
|  | 
 | ||||||
|  |     furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test); | ||||||
|  |     while(!furi_hal_subghz_is_async_tx_complete()) { | ||||||
|  |         furi_delay_ms(10); | ||||||
|  |     } | ||||||
|  |     furi_hal_subghz_stop_async_tx(); | ||||||
|  |     furi_hal_subghz_sleep(); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(subghz_hal_async_tx_test) { | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeNormal), | ||||||
|  |         "Test furi_hal_async_tx normal"); | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidStart), | ||||||
|  |         "Test furi_hal_async_tx invalid start"); | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidMid), | ||||||
|  |         "Test furi_hal_async_tx invalid mid"); | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidEnd), | ||||||
|  |         "Test furi_hal_async_tx invalid end"); | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetStart), | ||||||
|  |         "Test furi_hal_async_tx reset start"); | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetMid), | ||||||
|  |         "Test furi_hal_async_tx reset mid"); | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetEnd), | ||||||
|  |         "Test furi_hal_async_tx reset end"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //test decoders
 | //test decoders
 | ||||||
| MU_TEST(subghz_decoder_came_atomo_test) { | MU_TEST(subghz_decoder_came_atomo_test) { | ||||||
|     mu_assert( |     mu_assert( | ||||||
| @ -437,6 +580,13 @@ MU_TEST(subghz_decoder_clemsa_test) { | |||||||
|         "Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); |         "Test decoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | MU_TEST(subghz_decoder_ansonic_test) { | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_decoder_test( | ||||||
|  |             EXT_PATH("unit_tests/subghz/ansonic_raw.sub"), SUBGHZ_PROTOCOL_ANSONIC_NAME), | ||||||
|  |         "Test decoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //test encoders
 | //test encoders
 | ||||||
| MU_TEST(subghz_encoder_princeton_test) { | MU_TEST(subghz_encoder_princeton_test) { | ||||||
|     mu_assert( |     mu_assert( | ||||||
| @ -558,6 +708,12 @@ MU_TEST(subghz_encoder_clemsa_test) { | |||||||
|         "Test encoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); |         "Test encoder " SUBGHZ_PROTOCOL_CLEMSA_NAME " error\r\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | MU_TEST(subghz_encoder_ansonic_test) { | ||||||
|  |     mu_assert( | ||||||
|  |         subghz_encoder_test(EXT_PATH("unit_tests/subghz/ansonic.sub")), | ||||||
|  |         "Test encoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| MU_TEST(subghz_random_test) { | MU_TEST(subghz_random_test) { | ||||||
|     mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); |     mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); | ||||||
| } | } | ||||||
| @ -566,6 +722,8 @@ MU_TEST_SUITE(subghz) { | |||||||
|     subghz_test_init(); |     subghz_test_init(); | ||||||
|     MU_RUN_TEST(subghz_keystore_test); |     MU_RUN_TEST(subghz_keystore_test); | ||||||
| 
 | 
 | ||||||
|  |     MU_RUN_TEST(subghz_hal_async_tx_test); | ||||||
|  | 
 | ||||||
|     MU_RUN_TEST(subghz_decoder_came_atomo_test); |     MU_RUN_TEST(subghz_decoder_came_atomo_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_came_test); |     MU_RUN_TEST(subghz_decoder_came_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_came_twee_test); |     MU_RUN_TEST(subghz_decoder_came_twee_test); | ||||||
| @ -598,6 +756,7 @@ MU_TEST_SUITE(subghz) { | |||||||
|     MU_RUN_TEST(subghz_decoder_magellan_test); |     MU_RUN_TEST(subghz_decoder_magellan_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_intertechno_v3_test); |     MU_RUN_TEST(subghz_decoder_intertechno_v3_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_clemsa_test); |     MU_RUN_TEST(subghz_decoder_clemsa_test); | ||||||
|  |     MU_RUN_TEST(subghz_decoder_ansonic_test); | ||||||
| 
 | 
 | ||||||
|     MU_RUN_TEST(subghz_encoder_princeton_test); |     MU_RUN_TEST(subghz_encoder_princeton_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_came_test); |     MU_RUN_TEST(subghz_encoder_came_test); | ||||||
| @ -619,6 +778,7 @@ MU_TEST_SUITE(subghz) { | |||||||
|     MU_RUN_TEST(subghz_encoder_magellan_test); |     MU_RUN_TEST(subghz_encoder_magellan_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_intertechno_v3_test); |     MU_RUN_TEST(subghz_encoder_intertechno_v3_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_clemsa_test); |     MU_RUN_TEST(subghz_encoder_clemsa_test); | ||||||
|  |     MU_RUN_TEST(subghz_encoder_ansonic_test); | ||||||
| 
 | 
 | ||||||
|     MU_RUN_TEST(subghz_random_test); |     MU_RUN_TEST(subghz_random_test); | ||||||
|     subghz_test_deinit(); |     subghz_test_deinit(); | ||||||
|  | |||||||
| @ -265,8 +265,7 @@ void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { | |||||||
|                     offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1; |                     offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1; | ||||||
|                 } |                 } | ||||||
|                 if(offset_new > 0) { |                 if(offset_new > 0) { | ||||||
|                     offset_new = |                     offset_new = CLAMP(offset_new, (int32_t)model->item_cnt, 0); | ||||||
|                         CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0); |  | ||||||
|                 } else { |                 } else { | ||||||
|                     offset_new = 0; |                     offset_new = 0; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -3,9 +3,9 @@ | |||||||
| #include "../archive_i.h" | #include "../archive_i.h" | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| #define TAB_RIGHT InputKeyRight // Default tab swith direction
 | #define TAB_RIGHT InputKeyRight // Default tab switch direction
 | ||||||
| #define TAB_DEFAULT ArchiveTabFavorites // Start tab
 | #define TAB_DEFAULT ArchiveTabFavorites // Start tab
 | ||||||
| #define FILE_LIST_BUF_LEN 100 | #define FILE_LIST_BUF_LEN 50 | ||||||
| 
 | 
 | ||||||
| static const char* tab_default_paths[] = { | static const char* tab_default_paths[] = { | ||||||
|     [ArchiveTabFavorites] = "/app:favorites", |     [ArchiveTabFavorites] = "/app:favorites", | ||||||
|  | |||||||
| @ -657,12 +657,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { | |||||||
|     bad_usb->st.state = BadUsbStateInit; |     bad_usb->st.state = BadUsbStateInit; | ||||||
|     bad_usb->st.error[0] = '\0'; |     bad_usb->st.error[0] = '\0'; | ||||||
| 
 | 
 | ||||||
|     bad_usb->thread = furi_thread_alloc(); |     bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); | ||||||
|     furi_thread_set_name(bad_usb->thread, "BadUsbWorker"); |  | ||||||
|     furi_thread_set_stack_size(bad_usb->thread, 2048); |  | ||||||
|     furi_thread_set_context(bad_usb->thread, bad_usb); |  | ||||||
|     furi_thread_set_callback(bad_usb->thread, bad_usb_worker); |  | ||||||
| 
 |  | ||||||
|     furi_thread_start(bad_usb->thread); |     furi_thread_start(bad_usb->thread); | ||||||
|     return bad_usb; |     return bad_usb; | ||||||
| } | } | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ bool elf_resolve_from_hashtable(const char* name, Elf32_Addr* address) { | |||||||
| 
 | 
 | ||||||
|     auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); |     auto find_res = std::lower_bound(elf_api_table.cbegin(), elf_api_table.cend(), key); | ||||||
|     if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { |     if((find_res == elf_api_table.cend() || (find_res->hash != gnu_sym_hash))) { | ||||||
|         FURI_LOG_W(TAG, "Cant find symbol '%s' (hash %lx)!", name, gnu_sym_hash); |         FURI_LOG_W(TAG, "Can't find symbol '%s' (hash %lx)!", name, gnu_sym_hash); | ||||||
|         result = false; |         result = false; | ||||||
|     } else { |     } else { | ||||||
|         result = true; |         result = true; | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ struct GpioApp { | |||||||
|     GpioTest* gpio_test; |     GpioTest* gpio_test; | ||||||
|     GpioUsbUart* gpio_usb_uart; |     GpioUsbUart* gpio_usb_uart; | ||||||
|     UsbUartBridge* usb_uart_bridge; |     UsbUartBridge* usb_uart_bridge; | ||||||
|  |     UsbUartConfig* usb_uart_cfg; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  | |||||||
| @ -9,4 +9,5 @@ typedef enum { | |||||||
|     GpioCustomEventErrorBack, |     GpioCustomEventErrorBack, | ||||||
| 
 | 
 | ||||||
|     GpioUsbUartEventConfig, |     GpioUsbUartEventConfig, | ||||||
|  |     GpioUsbUartEventConfigSet, | ||||||
| } GpioCustomEvent; | } GpioCustomEvent; | ||||||
|  | |||||||
| @ -9,8 +9,6 @@ typedef enum { | |||||||
|     UsbUartLineIndexFlow, |     UsbUartLineIndexFlow, | ||||||
| } LineIndex; | } LineIndex; | ||||||
| 
 | 
 | ||||||
| static UsbUartConfig* cfg_set; |  | ||||||
| 
 |  | ||||||
| static const char* vcp_ch[] = {"0 (CLI)", "1"}; | static const char* vcp_ch[] = {"0 (CLI)", "1"}; | ||||||
| static const char* uart_ch[] = {"13,14", "15,16"}; | static const char* uart_ch[] = {"13,14", "15,16"}; | ||||||
| static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"}; | static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"}; | ||||||
| @ -28,8 +26,14 @@ static const uint32_t baudrate_list[] = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) { | bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) { | ||||||
|     UNUSED(context); |     GpioApp* app = context; | ||||||
|     UNUSED(event); |     furi_assert(app); | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GpioUsbUartEventConfigSet) { | ||||||
|  |             usb_uart_set_config(app->usb_uart_bridge, app->usb_uart_cfg); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -38,55 +42,59 @@ void line_ensure_flow_invariant(GpioApp* app) { | |||||||
|     // selected. This function enforces that invariant by resetting flow_pins
 |     // selected. This function enforces that invariant by resetting flow_pins
 | ||||||
|     // to None if it is configured to 16,15 when LPUART is selected.
 |     // to None if it is configured to 16,15 when LPUART is selected.
 | ||||||
| 
 | 
 | ||||||
|     uint8_t available_flow_pins = cfg_set->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; |     uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; | ||||||
|     VariableItem* item = app->var_item_flow; |     VariableItem* item = app->var_item_flow; | ||||||
|     variable_item_set_values_count(item, available_flow_pins); |     variable_item_set_values_count(item, available_flow_pins); | ||||||
| 
 | 
 | ||||||
|     if(cfg_set->flow_pins >= available_flow_pins) { |     if(app->usb_uart_cfg->flow_pins >= available_flow_pins) { | ||||||
|         cfg_set->flow_pins = 0; |         app->usb_uart_cfg->flow_pins = 0; | ||||||
|         usb_uart_set_config(app->usb_uart_bridge, cfg_set); |  | ||||||
| 
 | 
 | ||||||
|         variable_item_set_current_value_index(item, cfg_set->flow_pins); |         variable_item_set_current_value_index(item, app->usb_uart_cfg->flow_pins); | ||||||
|         variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]); |         variable_item_set_current_value_text(item, flow_pins[app->usb_uart_cfg->flow_pins]); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void line_vcp_cb(VariableItem* item) { | static void line_vcp_cb(VariableItem* item) { | ||||||
|     GpioApp* app = variable_item_get_context(item); |     GpioApp* app = variable_item_get_context(item); | ||||||
|  |     furi_assert(app); | ||||||
|     uint8_t index = variable_item_get_current_value_index(item); |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
| 
 | 
 | ||||||
|     variable_item_set_current_value_text(item, vcp_ch[index]); |     variable_item_set_current_value_text(item, vcp_ch[index]); | ||||||
| 
 | 
 | ||||||
|     cfg_set->vcp_ch = index; |     app->usb_uart_cfg->vcp_ch = index; | ||||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); |     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void line_port_cb(VariableItem* item) { | static void line_port_cb(VariableItem* item) { | ||||||
|     GpioApp* app = variable_item_get_context(item); |     GpioApp* app = variable_item_get_context(item); | ||||||
|  |     furi_assert(app); | ||||||
|     uint8_t index = variable_item_get_current_value_index(item); |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
| 
 | 
 | ||||||
|     variable_item_set_current_value_text(item, uart_ch[index]); |     variable_item_set_current_value_text(item, uart_ch[index]); | ||||||
| 
 | 
 | ||||||
|     if(index == 0) |     if(index == 0) | ||||||
|         cfg_set->uart_ch = FuriHalUartIdUSART1; |         app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1; | ||||||
|     else if(index == 1) |     else if(index == 1) | ||||||
|         cfg_set->uart_ch = FuriHalUartIdLPUART1; |         app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1; | ||||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); | 
 | ||||||
|     line_ensure_flow_invariant(app); |     line_ensure_flow_invariant(app); | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void line_flow_cb(VariableItem* item) { | static void line_flow_cb(VariableItem* item) { | ||||||
|     GpioApp* app = variable_item_get_context(item); |     GpioApp* app = variable_item_get_context(item); | ||||||
|  |     furi_assert(app); | ||||||
|     uint8_t index = variable_item_get_current_value_index(item); |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
| 
 | 
 | ||||||
|     variable_item_set_current_value_text(item, flow_pins[index]); |     variable_item_set_current_value_text(item, flow_pins[index]); | ||||||
| 
 | 
 | ||||||
|     cfg_set->flow_pins = index; |     app->usb_uart_cfg->flow_pins = index; | ||||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); |     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void line_baudrate_cb(VariableItem* item) { | static void line_baudrate_cb(VariableItem* item) { | ||||||
|     GpioApp* app = variable_item_get_context(item); |     GpioApp* app = variable_item_get_context(item); | ||||||
|  |     furi_assert(app); | ||||||
|     uint8_t index = variable_item_get_current_value_index(item); |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
| 
 | 
 | ||||||
|     char br_text[8]; |     char br_text[8]; | ||||||
| @ -94,28 +102,29 @@ static void line_baudrate_cb(VariableItem* item) { | |||||||
|     if(index > 0) { |     if(index > 0) { | ||||||
|         snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); |         snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); | ||||||
|         variable_item_set_current_value_text(item, br_text); |         variable_item_set_current_value_text(item, br_text); | ||||||
|         cfg_set->baudrate = baudrate_list[index - 1]; |         app->usb_uart_cfg->baudrate = baudrate_list[index - 1]; | ||||||
|     } else { |     } else { | ||||||
|         variable_item_set_current_value_text(item, baudrate_mode[index]); |         variable_item_set_current_value_text(item, baudrate_mode[index]); | ||||||
|         cfg_set->baudrate = 0; |         app->usb_uart_cfg->baudrate = 0; | ||||||
|     } |     } | ||||||
|     cfg_set->baudrate_mode = index; |     app->usb_uart_cfg->baudrate_mode = index; | ||||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); |     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gpio_scene_usb_uart_cfg_on_enter(void* context) { | void gpio_scene_usb_uart_cfg_on_enter(void* context) { | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|  |     furi_assert(app); | ||||||
|     VariableItemList* var_item_list = app->var_item_list; |     VariableItemList* var_item_list = app->var_item_list; | ||||||
| 
 | 
 | ||||||
|     cfg_set = malloc(sizeof(UsbUartConfig)); |     app->usb_uart_cfg = malloc(sizeof(UsbUartConfig)); | ||||||
|     usb_uart_get_config(app->usb_uart_bridge, cfg_set); |     usb_uart_get_config(app->usb_uart_bridge, app->usb_uart_cfg); | ||||||
| 
 | 
 | ||||||
|     VariableItem* item; |     VariableItem* item; | ||||||
|     char br_text[8]; |     char br_text[8]; | ||||||
| 
 | 
 | ||||||
|     item = variable_item_list_add(var_item_list, "USB Channel", 2, line_vcp_cb, app); |     item = variable_item_list_add(var_item_list, "USB Channel", 2, line_vcp_cb, app); | ||||||
|     variable_item_set_current_value_index(item, cfg_set->vcp_ch); |     variable_item_set_current_value_index(item, app->usb_uart_cfg->vcp_ch); | ||||||
|     variable_item_set_current_value_text(item, vcp_ch[cfg_set->vcp_ch]); |     variable_item_set_current_value_text(item, vcp_ch[app->usb_uart_cfg->vcp_ch]); | ||||||
| 
 | 
 | ||||||
|     item = variable_item_list_add( |     item = variable_item_list_add( | ||||||
|         var_item_list, |         var_item_list, | ||||||
| @ -123,22 +132,23 @@ void gpio_scene_usb_uart_cfg_on_enter(void* context) { | |||||||
|         sizeof(baudrate_list) / sizeof(baudrate_list[0]) + 1, |         sizeof(baudrate_list) / sizeof(baudrate_list[0]) + 1, | ||||||
|         line_baudrate_cb, |         line_baudrate_cb, | ||||||
|         app); |         app); | ||||||
|     variable_item_set_current_value_index(item, cfg_set->baudrate_mode); |     variable_item_set_current_value_index(item, app->usb_uart_cfg->baudrate_mode); | ||||||
|     if(cfg_set->baudrate_mode > 0) { |     if(app->usb_uart_cfg->baudrate_mode > 0) { | ||||||
|         snprintf(br_text, 7, "%lu", baudrate_list[cfg_set->baudrate_mode - 1]); |         snprintf(br_text, 7, "%lu", baudrate_list[app->usb_uart_cfg->baudrate_mode - 1]); | ||||||
|         variable_item_set_current_value_text(item, br_text); |         variable_item_set_current_value_text(item, br_text); | ||||||
|     } else { |     } else { | ||||||
|         variable_item_set_current_value_text(item, baudrate_mode[cfg_set->baudrate_mode]); |         variable_item_set_current_value_text( | ||||||
|  |             item, baudrate_mode[app->usb_uart_cfg->baudrate_mode]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     item = variable_item_list_add(var_item_list, "UART Pins", 2, line_port_cb, app); |     item = variable_item_list_add(var_item_list, "UART Pins", 2, line_port_cb, app); | ||||||
|     variable_item_set_current_value_index(item, cfg_set->uart_ch); |     variable_item_set_current_value_index(item, app->usb_uart_cfg->uart_ch); | ||||||
|     variable_item_set_current_value_text(item, uart_ch[cfg_set->uart_ch]); |     variable_item_set_current_value_text(item, uart_ch[app->usb_uart_cfg->uart_ch]); | ||||||
| 
 | 
 | ||||||
|     item = variable_item_list_add( |     item = variable_item_list_add( | ||||||
|         var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app); |         var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app); | ||||||
|     variable_item_set_current_value_index(item, cfg_set->flow_pins); |     variable_item_set_current_value_index(item, app->usb_uart_cfg->flow_pins); | ||||||
|     variable_item_set_current_value_text(item, flow_pins[cfg_set->flow_pins]); |     variable_item_set_current_value_text(item, flow_pins[app->usb_uart_cfg->flow_pins]); | ||||||
|     app->var_item_flow = item; |     app->var_item_flow = item; | ||||||
|     line_ensure_flow_invariant(app); |     line_ensure_flow_invariant(app); | ||||||
| 
 | 
 | ||||||
| @ -155,5 +165,5 @@ void gpio_scene_usb_uart_cfg_on_exit(void* context) { | |||||||
|         GpioAppViewUsbUartCfg, |         GpioAppViewUsbUartCfg, | ||||||
|         variable_item_list_get_selected_item_index(app->var_item_list)); |         variable_item_list_get_selected_item_index(app->var_item_list)); | ||||||
|     variable_item_list_reset(app->var_item_list); |     variable_item_list_reset(app->var_item_list); | ||||||
|     free(cfg_set); |     free(app->usb_uart_cfg); | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| #include <furi_hal_usb_cdc.h> | #include <furi_hal_usb_cdc.h> | ||||||
| #include "usb_cdc.h" | #include "usb_cdc.h" | ||||||
| #include "cli/cli_vcp.h" | #include "cli/cli_vcp.h" | ||||||
|  | #include <toolbox/api_lock.h> | ||||||
| #include "cli/cli.h" | #include "cli/cli.h" | ||||||
| 
 | 
 | ||||||
| #define USB_CDC_PKT_LEN CDC_DATA_SZ | #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||||
| @ -51,6 +52,8 @@ struct UsbUartBridge { | |||||||
| 
 | 
 | ||||||
|     UsbUartState st; |     UsbUartState st; | ||||||
| 
 | 
 | ||||||
|  |     FuriApiLock cfg_lock; | ||||||
|  | 
 | ||||||
|     uint8_t rx_buf[USB_CDC_PKT_LEN]; |     uint8_t rx_buf[USB_CDC_PKT_LEN]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -159,11 +162,8 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|     usb_uart->tx_sem = furi_semaphore_alloc(1, 1); |     usb_uart->tx_sem = furi_semaphore_alloc(1, 1); | ||||||
|     usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); |     usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); | ||||||
| 
 | 
 | ||||||
|     usb_uart->tx_thread = furi_thread_alloc(); |     usb_uart->tx_thread = | ||||||
|     furi_thread_set_name(usb_uart->tx_thread, "UsbUartTxWorker"); |         furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart); | ||||||
|     furi_thread_set_stack_size(usb_uart->tx_thread, 512); |  | ||||||
|     furi_thread_set_context(usb_uart->tx_thread, usb_uart); |  | ||||||
|     furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); |  | ||||||
| 
 | 
 | ||||||
|     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); |     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); | ||||||
|     usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); |     usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); | ||||||
| @ -247,6 +247,7 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|                 usb_uart->cfg.flow_pins = usb_uart->cfg_new.flow_pins; |                 usb_uart->cfg.flow_pins = usb_uart->cfg_new.flow_pins; | ||||||
|                 events |= WorkerEvtCtrlLineSet; |                 events |= WorkerEvtCtrlLineSet; | ||||||
|             } |             } | ||||||
|  |             api_lock_unlock(usb_uart->cfg_lock); | ||||||
|         } |         } | ||||||
|         if(events & WorkerEvtLineCfgSet) { |         if(events & WorkerEvtLineCfgSet) { | ||||||
|             if(usb_uart->cfg.baudrate == 0) |             if(usb_uart->cfg.baudrate == 0) | ||||||
| @ -338,11 +339,7 @@ UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { | |||||||
| 
 | 
 | ||||||
|     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); |     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); | ||||||
| 
 | 
 | ||||||
|     usb_uart->thread = furi_thread_alloc(); |     usb_uart->thread = furi_thread_alloc_ex("UsbUartWorker", 1024, usb_uart_worker, usb_uart); | ||||||
|     furi_thread_set_name(usb_uart->thread, "UsbUartWorker"); |  | ||||||
|     furi_thread_set_stack_size(usb_uart->thread, 1024); |  | ||||||
|     furi_thread_set_context(usb_uart->thread, usb_uart); |  | ||||||
|     furi_thread_set_callback(usb_uart->thread, usb_uart_worker); |  | ||||||
| 
 | 
 | ||||||
|     furi_thread_start(usb_uart->thread); |     furi_thread_start(usb_uart->thread); | ||||||
|     return usb_uart; |     return usb_uart; | ||||||
| @ -359,8 +356,10 @@ void usb_uart_disable(UsbUartBridge* usb_uart) { | |||||||
| void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | ||||||
|     furi_assert(usb_uart); |     furi_assert(usb_uart); | ||||||
|     furi_assert(cfg); |     furi_assert(cfg); | ||||||
|  |     usb_uart->cfg_lock = api_lock_alloc_locked(); | ||||||
|     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); |     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); | ||||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCfgChange); |     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCfgChange); | ||||||
|  |     api_lock_wait_unlock_and_free(usb_uart->cfg_lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void usb_uart_get_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | void usb_uart_get_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | |||||||
|         &I_Power_hvr_25x27, |         &I_Power_hvr_25x27, | ||||||
|         infrared_scene_universal_common_item_callback, |         infrared_scene_universal_common_item_callback, | ||||||
|         context); |         context); | ||||||
|     infrared_brute_force_add_record(brute_force, i++, "POWER"); |     infrared_brute_force_add_record(brute_force, i++, "Power"); | ||||||
|     button_panel_add_item( |     button_panel_add_item( | ||||||
|         button_panel, |         button_panel, | ||||||
|         i, |         i, | ||||||
| @ -36,7 +36,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | |||||||
|         &I_Mute_hvr_25x27, |         &I_Mute_hvr_25x27, | ||||||
|         infrared_scene_universal_common_item_callback, |         infrared_scene_universal_common_item_callback, | ||||||
|         context); |         context); | ||||||
|     infrared_brute_force_add_record(brute_force, i++, "MUTE"); |     infrared_brute_force_add_record(brute_force, i++, "Mute"); | ||||||
|     button_panel_add_item( |     button_panel_add_item( | ||||||
|         button_panel, |         button_panel, | ||||||
|         i, |         i, | ||||||
| @ -48,7 +48,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | |||||||
|         &I_Vol_up_hvr_25x27, |         &I_Vol_up_hvr_25x27, | ||||||
|         infrared_scene_universal_common_item_callback, |         infrared_scene_universal_common_item_callback, | ||||||
|         context); |         context); | ||||||
|     infrared_brute_force_add_record(brute_force, i++, "VOL+"); |     infrared_brute_force_add_record(brute_force, i++, "Vol_up"); | ||||||
|     button_panel_add_item( |     button_panel_add_item( | ||||||
|         button_panel, |         button_panel, | ||||||
|         i, |         i, | ||||||
| @ -60,7 +60,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | |||||||
|         &I_Up_hvr_25x27, |         &I_Up_hvr_25x27, | ||||||
|         infrared_scene_universal_common_item_callback, |         infrared_scene_universal_common_item_callback, | ||||||
|         context); |         context); | ||||||
|     infrared_brute_force_add_record(brute_force, i++, "CH+"); |     infrared_brute_force_add_record(brute_force, i++, "Ch_next"); | ||||||
|     button_panel_add_item( |     button_panel_add_item( | ||||||
|         button_panel, |         button_panel, | ||||||
|         i, |         i, | ||||||
| @ -72,7 +72,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | |||||||
|         &I_Vol_down_hvr_25x27, |         &I_Vol_down_hvr_25x27, | ||||||
|         infrared_scene_universal_common_item_callback, |         infrared_scene_universal_common_item_callback, | ||||||
|         context); |         context); | ||||||
|     infrared_brute_force_add_record(brute_force, i++, "VOL-"); |     infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); | ||||||
|     button_panel_add_item( |     button_panel_add_item( | ||||||
|         button_panel, |         button_panel, | ||||||
|         i, |         i, | ||||||
| @ -84,7 +84,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | |||||||
|         &I_Down_hvr_25x27, |         &I_Down_hvr_25x27, | ||||||
|         infrared_scene_universal_common_item_callback, |         infrared_scene_universal_common_item_callback, | ||||||
|         context); |         context); | ||||||
|     infrared_brute_force_add_record(brute_force, i++, "CH-"); |     infrared_brute_force_add_record(brute_force, i++, "Ch_prev"); | ||||||
| 
 | 
 | ||||||
|     button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote"); |     button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote"); | ||||||
|     button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol"); |     button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol"); | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate) | |||||||
| ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth) | ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth) | ||||||
| ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult) | ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult) | ||||||
| ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) | ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) | ||||||
|  | ADD_SCENE(nfc, mf_ultralight_unlock_auto, MfUltralightUnlockAuto) | ||||||
| ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) | ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) | ||||||
| ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) | ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) | ||||||
| ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess) | ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess) | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexSave, |     SubmenuIndexSave, | ||||||
|     SubmenuIndexEmulate, |     SubmenuIndexEmulate, | ||||||
|  |     SubmenuIndexDetectReader, | ||||||
|     SubmenuIndexInfo, |     SubmenuIndexInfo, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -21,6 +22,14 @@ void nfc_scene_mf_classic_menu_on_enter(void* context) { | |||||||
|         submenu, "Save", SubmenuIndexSave, nfc_scene_mf_classic_menu_submenu_callback, nfc); |         submenu, "Save", SubmenuIndexSave, nfc_scene_mf_classic_menu_submenu_callback, nfc); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mf_classic_menu_submenu_callback, nfc); |         submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_mf_classic_menu_submenu_callback, nfc); | ||||||
|  |     if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Detect reader", | ||||||
|  |             SubmenuIndexDetectReader, | ||||||
|  |             nfc_scene_mf_classic_menu_submenu_callback, | ||||||
|  |             nfc); | ||||||
|  |     } | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_classic_menu_submenu_callback, nfc); |         submenu, "Info", SubmenuIndexInfo, nfc_scene_mf_classic_menu_submenu_callback, nfc); | ||||||
| 
 | 
 | ||||||
| @ -35,17 +44,14 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) | |||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu, event.event); | ||||||
|         if(event.event == SubmenuIndexSave) { |         if(event.event == SubmenuIndexSave) { | ||||||
|             scene_manager_set_scene_state( |  | ||||||
|                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave); |  | ||||||
|             nfc->dev->format = NfcDeviceSaveFormatMifareClassic; |             nfc->dev->format = NfcDeviceSaveFormatMifareClassic; | ||||||
|             // Clear device name
 |             // Clear device name
 | ||||||
|             nfc_device_set_name(nfc->dev, ""); |             nfc_device_set_name(nfc->dev, ""); | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexEmulate) { |         } else if(event.event == SubmenuIndexEmulate) { | ||||||
|             scene_manager_set_scene_state( |  | ||||||
|                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexEmulate); |  | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); | ||||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { |             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { | ||||||
|                 DOLPHIN_DEED(DolphinDeedNfcAddEmulate); |                 DOLPHIN_DEED(DolphinDeedNfcAddEmulate); | ||||||
| @ -53,9 +59,11 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) | |||||||
|                 DOLPHIN_DEED(DolphinDeedNfcEmulate); |                 DOLPHIN_DEED(DolphinDeedNfcEmulate); | ||||||
|             } |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexDetectReader) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); | ||||||
|  |             DOLPHIN_DEED(DolphinDeedNfcDetectReader); | ||||||
|  |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexInfo) { |         } else if(event.event == SubmenuIndexInfo) { | ||||||
|             scene_manager_set_scene_state( |  | ||||||
|                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexInfo); |  | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -19,10 +19,10 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { | |||||||
|     Submenu* submenu = nfc->submenu; |     Submenu* submenu = nfc->submenu; | ||||||
|     MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; |     MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data; | ||||||
| 
 | 
 | ||||||
|     if(data->data_read != data->data_size) { |     if(!mf_ul_is_full_capture(data)) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, |             submenu, | ||||||
|             "Unlock With Password", |             "Unlock", | ||||||
|             SubmenuIndexUnlock, |             SubmenuIndexUnlock, | ||||||
|             nfc_scene_mf_ultralight_menu_submenu_callback, |             nfc_scene_mf_ultralight_menu_submenu_callback, | ||||||
|             nfc); |             nfc); | ||||||
|  | |||||||
| @ -24,25 +24,29 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState | |||||||
|     if(curr_state != state) { |     if(curr_state != state) { | ||||||
|         if(state == NfcSceneMfUlReadStateDetecting) { |         if(state == NfcSceneMfUlReadStateDetecting) { | ||||||
|             popup_reset(nfc->popup); |             popup_reset(nfc->popup); | ||||||
|             popup_set_text( |             popup_set_text(nfc->popup, "Apply the\ntarget card", 97, 24, AlignCenter, AlignTop); | ||||||
|                 nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); |  | ||||||
|             popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); |             popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |             nfc_blink_read_start(nfc); | ||||||
|         } else if(state == NfcSceneMfUlReadStateReading) { |         } else if(state == NfcSceneMfUlReadStateReading) { | ||||||
|             popup_reset(nfc->popup); |             popup_reset(nfc->popup); | ||||||
|             popup_set_header( |             popup_set_header( | ||||||
|                 nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); |                 nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); | ||||||
|             popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); |             popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); | ||||||
|  |             nfc_blink_detect_start(nfc); | ||||||
|         } else if(state == NfcSceneMfUlReadStateNotSupportedCard) { |         } else if(state == NfcSceneMfUlReadStateNotSupportedCard) { | ||||||
|             popup_reset(nfc->popup); |             popup_reset(nfc->popup); | ||||||
|             popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop); |             popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop); | ||||||
|             popup_set_text( |             popup_set_text( | ||||||
|                 nfc->popup, |                 nfc->popup, | ||||||
|                 "Only MIFARE\nUltralight & NTAG\n are supported", |                 "Only MIFARE\nUltralight & NTAG\nare supported", | ||||||
|                 4, |                 4, | ||||||
|                 22, |                 22, | ||||||
|                 AlignLeft, |                 AlignLeft, | ||||||
|                 AlignTop); |                 AlignTop); | ||||||
|             popup_set_icon(nfc->popup, 73, 20, &I_DolphinCommon_56x48); |             popup_set_icon(nfc->popup, 73, 20, &I_DolphinCommon_56x48); | ||||||
|  |             nfc_blink_stop(nfc); | ||||||
|  |             notification_message(nfc->notifications, &sequence_error); | ||||||
|  |             notification_message(nfc->notifications, &sequence_set_red_255); | ||||||
|         } |         } | ||||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state); |         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state); | ||||||
|     } |     } | ||||||
| @ -62,8 +66,6 @@ void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { | |||||||
|         &nfc->dev->dev_data, |         &nfc->dev->dev_data, | ||||||
|         nfc_scene_mf_ultralight_read_auth_worker_callback, |         nfc_scene_mf_ultralight_read_auth_worker_callback, | ||||||
|         nfc); |         nfc); | ||||||
| 
 |  | ||||||
|     nfc_blink_read_start(nfc); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) { | bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) { | ||||||
| @ -86,8 +88,17 @@ bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent | |||||||
|                 nfc, NfcSceneMfUlReadStateNotSupportedCard); |                 nfc, NfcSceneMfUlReadStateNotSupportedCard); | ||||||
|         } |         } | ||||||
|     } else if(event.type == SceneManagerEventTypeBack) { |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( |         MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; | ||||||
|             nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); |         NfcScene next_scene; | ||||||
|  |         if(mf_ul_data->auth_method == MfUltralightAuthMethodManual) { | ||||||
|  |             next_scene = NfcSceneMfUltralightKeyInput; | ||||||
|  |         } else if(mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { | ||||||
|  |             next_scene = NfcSceneMfUltralightUnlockAuto; | ||||||
|  |         } else { | ||||||
|  |             next_scene = NfcSceneMfUltralightUnlockMenu; | ||||||
|  |         } | ||||||
|  |         consumed = | ||||||
|  |             scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); | ||||||
|     } |     } | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,16 +19,20 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { | |||||||
|     MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; |     MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; | ||||||
|     MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); |     MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); | ||||||
|     Widget* widget = nfc->widget; |     Widget* widget = nfc->widget; | ||||||
|  |     const char* title; | ||||||
|     FuriString* temp_str; |     FuriString* temp_str; | ||||||
|     temp_str = furi_string_alloc(); |     temp_str = furi_string_alloc(); | ||||||
| 
 | 
 | ||||||
|     if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { |     if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { | ||||||
|         widget_add_string_element( |         if(mf_ul_data->auth_success) { | ||||||
|             widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "All pages are unlocked!"); |             title = "All pages are unlocked!"; | ||||||
|  |         } else { | ||||||
|  |             title = "All unlocked but failed auth!"; | ||||||
|  |         } | ||||||
|     } else { |     } else { | ||||||
|         widget_add_string_element( |         title = "Not all pages unlocked!"; | ||||||
|             widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Not all pages unlocked!"); |  | ||||||
|     } |     } | ||||||
|  |     widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); | ||||||
|     furi_string_set(temp_str, "UID:"); |     furi_string_set(temp_str, "UID:"); | ||||||
|     for(size_t i = 0; i < nfc_data->uid_len; i++) { |     for(size_t i = 0; i < nfc_data->uid_len; i++) { | ||||||
|         furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); |         furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]); | ||||||
| @ -65,6 +69,7 @@ void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) { | |||||||
|         nfc); |         nfc); | ||||||
| 
 | 
 | ||||||
|     furi_string_free(temp_str); |     furi_string_free(temp_str); | ||||||
|  |     notification_message(nfc->notifications, &sequence_set_green_255); | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -81,8 +86,21 @@ bool nfc_scene_mf_ultralight_read_auth_result_on_event(void* context, SceneManag | |||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|     } else if(event.type == SceneManagerEventTypeBack) { |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( |         MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; | ||||||
|             nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); |         if(mf_ul_data->auth_method == MfUltralightAuthMethodManual || | ||||||
|  |            mf_ul_data->auth_method == MfUltralightAuthMethodAuto) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc->scene_manager); | ||||||
|  |         } else { | ||||||
|  |             NfcScene next_scene; | ||||||
|  |             if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { | ||||||
|  |                 next_scene = NfcSceneMfUltralightMenu; | ||||||
|  |             } else { | ||||||
|  |                 next_scene = NfcSceneMfUltralightUnlockMenu; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             consumed = | ||||||
|  |                 scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, next_scene); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -93,4 +111,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_exit(void* context) { | |||||||
| 
 | 
 | ||||||
|     // Clean views
 |     // Clean views
 | ||||||
|     widget_reset(nfc->widget); |     widget_reset(nfc->widget); | ||||||
|  | 
 | ||||||
|  |     notification_message_block(nfc->notifications, &sequence_reset_green); | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,64 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_ultralight_unlock_auto_worker_callback(NfcWorkerEvent event, void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, event); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_unlock_auto_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Setup view
 | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         nfc->widget, | ||||||
|  |         54, | ||||||
|  |         30, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignCenter, | ||||||
|  |         FontPrimary, | ||||||
|  |         "Touch the\nreader to get\npassword..."); | ||||||
|  |     widget_add_icon_element(nfc->widget, 0, 15, &I_Modern_reader_18x34); | ||||||
|  |     widget_add_icon_element(nfc->widget, 20, 12, &I_Move_flipper_26x39); | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
|  | 
 | ||||||
|  |     // Start worker
 | ||||||
|  |     nfc_worker_start( | ||||||
|  |         nfc->worker, | ||||||
|  |         NfcWorkerStateMfUltralightEmulate, | ||||||
|  |         &nfc->dev->dev_data, | ||||||
|  |         nfc_scene_mf_ultralight_unlock_auto_worker_callback, | ||||||
|  |         nfc); | ||||||
|  | 
 | ||||||
|  |     nfc_blink_read_start(nfc); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_ultralight_unlock_auto_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if((event.event == NfcWorkerEventMfUltralightPwdAuth)) { | ||||||
|  |             MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; | ||||||
|  |             memcpy(nfc->byte_input_store, auth->pwd.raw, sizeof(auth->pwd.raw)); | ||||||
|  |             nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAuto; | ||||||
|  |             nfc_worker_stop(nfc->worker); | ||||||
|  |             notification_message(nfc->notifications, &sequence_success); | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_ultralight_unlock_auto_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Stop worker
 | ||||||
|  |     nfc_worker_stop(nfc->worker); | ||||||
|  |     // Clear view
 | ||||||
|  |     widget_reset(nfc->widget); | ||||||
|  | 
 | ||||||
|  |     nfc_blink_stop(nfc); | ||||||
|  | } | ||||||
| @ -1,9 +1,10 @@ | |||||||
| #include "../nfc_i.h" | #include "../nfc_i.h" | ||||||
| 
 | 
 | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexMfUlUnlockMenuManual, |     SubmenuIndexMfUlUnlockMenuAuto, | ||||||
|     SubmenuIndexMfUlUnlockMenuAmeebo, |     SubmenuIndexMfUlUnlockMenuAmeebo, | ||||||
|     SubmenuIndexMfUlUnlockMenuXiaomi, |     SubmenuIndexMfUlUnlockMenuXiaomi, | ||||||
|  |     SubmenuIndexMfUlUnlockMenuManual, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void nfc_scene_mf_ultralight_unlock_menu_submenu_callback(void* context, uint32_t index) { | void nfc_scene_mf_ultralight_unlock_menu_submenu_callback(void* context, uint32_t index) { | ||||||
| @ -18,12 +19,14 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     uint32_t state = |     uint32_t state = | ||||||
|         scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); |         scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||||
|     submenu_add_item( |     if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { | ||||||
|         submenu, |         submenu_add_item( | ||||||
|         "Enter Password Manually", |             submenu, | ||||||
|         SubmenuIndexMfUlUnlockMenuManual, |             "Unlock With Reader", | ||||||
|         nfc_scene_mf_ultralight_unlock_menu_submenu_callback, |             SubmenuIndexMfUlUnlockMenuAuto, | ||||||
|         nfc); |             nfc_scene_mf_ultralight_unlock_menu_submenu_callback, | ||||||
|  |             nfc); | ||||||
|  |     } | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, |         submenu, | ||||||
|         "Auth As Ameebo", |         "Auth As Ameebo", | ||||||
| @ -32,10 +35,16 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { | |||||||
|         nfc); |         nfc); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, |         submenu, | ||||||
|         "Auth As Xiaomi", |         "Auth As Xiaomi Air Purifier", | ||||||
|         SubmenuIndexMfUlUnlockMenuXiaomi, |         SubmenuIndexMfUlUnlockMenuXiaomi, | ||||||
|         nfc_scene_mf_ultralight_unlock_menu_submenu_callback, |         nfc_scene_mf_ultralight_unlock_menu_submenu_callback, | ||||||
|         nfc); |         nfc); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Enter Password Manually", | ||||||
|  |         SubmenuIndexMfUlUnlockMenuManual, | ||||||
|  |         nfc_scene_mf_ultralight_unlock_menu_submenu_callback, | ||||||
|  |         nfc); | ||||||
|     submenu_set_selected_item(submenu, state); |     submenu_set_selected_item(submenu, state); | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); | ||||||
| } | } | ||||||
| @ -57,8 +66,12 @@ bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEve | |||||||
|             nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodXiaomi; |             nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodXiaomi; | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexMfUlUnlockMenuAuto) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); |         scene_manager_set_scene_state( | ||||||
|  |             nfc->scene_manager, NfcSceneMfUltralightUnlockMenu, event.event); | ||||||
|     } |     } | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,15 +10,43 @@ void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, | |||||||
| void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { | void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { | ||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
|     DialogEx* dialog_ex = nfc->dialog_ex; |     DialogEx* dialog_ex = nfc->dialog_ex; | ||||||
|  |     MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; | ||||||
| 
 | 
 | ||||||
|     dialog_ex_set_context(dialog_ex, nfc); |     dialog_ex_set_context(dialog_ex, nfc); | ||||||
|     dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback); |     dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback); | ||||||
| 
 | 
 | ||||||
|     dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); |     if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { | ||||||
|     dialog_ex_set_text( |         // Build dialog text
 | ||||||
|         dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); |         MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; | ||||||
|     dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); |         FuriString* password_str = | ||||||
|     dialog_ex_set_center_button_text(dialog_ex, "OK"); |             furi_string_alloc_set_str("Try to unlock the card with\npassword: "); | ||||||
|  |         for(size_t i = 0; i < sizeof(auth->pwd.raw); ++i) { | ||||||
|  |             furi_string_cat_printf(password_str, "%02X ", nfc->byte_input_store[i]); | ||||||
|  |         } | ||||||
|  |         furi_string_cat_str(password_str, "?\nCaution, a wrong password\ncan block the card!"); | ||||||
|  |         nfc_text_store_set(nfc, furi_string_get_cstr(password_str)); | ||||||
|  |         furi_string_free(password_str); | ||||||
|  | 
 | ||||||
|  |         dialog_ex_set_header( | ||||||
|  |             dialog_ex, | ||||||
|  |             auth_method == MfUltralightAuthMethodAuto ? "Password captured!" : "Risky function!", | ||||||
|  |             64, | ||||||
|  |             0, | ||||||
|  |             AlignCenter, | ||||||
|  |             AlignTop); | ||||||
|  |         dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 12, AlignCenter, AlignTop); | ||||||
|  |         dialog_ex_set_left_button_text(dialog_ex, "Cancel"); | ||||||
|  |         dialog_ex_set_right_button_text(dialog_ex, "Continue"); | ||||||
|  | 
 | ||||||
|  |         if(auth_method == MfUltralightAuthMethodAuto) | ||||||
|  |             notification_message(nfc->notifications, &sequence_set_green_255); | ||||||
|  |     } else { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); | ||||||
|  |         dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); | ||||||
|  |         dialog_ex_set_center_button_text(dialog_ex, "OK"); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); | ||||||
| } | } | ||||||
| @ -28,12 +56,33 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve | |||||||
| 
 | 
 | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; | ||||||
|         if(event.event == DialogExResultCenter) { |     if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); |         if(event.type == SceneManagerEventTypeCustom) { | ||||||
|             DOLPHIN_DEED(DolphinDeedNfcRead); |             if(event.event == DialogExResultRight) { | ||||||
|  |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); | ||||||
|  |                 DOLPHIN_DEED(DolphinDeedNfcRead); | ||||||
|  |                 consumed = true; | ||||||
|  |             } else if(event.event == DialogExResultLeft) { | ||||||
|  |                 if(auth_method == MfUltralightAuthMethodAuto) { | ||||||
|  |                     consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                         nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||||
|  |                 } else { | ||||||
|  |                     consumed = scene_manager_previous_scene(nfc->scene_manager); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|  |             // Cannot press back
 | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|  |     } else { | ||||||
|  |         if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |             if(event.event == DialogExResultCenter) { | ||||||
|  |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); | ||||||
|  |                 DOLPHIN_DEED(DolphinDeedNfcRead); | ||||||
|  |                 consumed = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -43,5 +92,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_exit(void* context) { | |||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
| 
 | 
 | ||||||
|     dialog_ex_reset(nfc->dialog_ex); |     dialog_ex_reset(nfc->dialog_ex); | ||||||
|     submenu_reset(nfc->submenu); |     nfc_text_store_clear(nfc); | ||||||
|  | 
 | ||||||
|  |     notification_message_block(nfc->notifications, &sequence_reset_green); | ||||||
| } | } | ||||||
|  | |||||||
| @ -87,6 +87,20 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { | |||||||
|             temp_str, "\nPages Read %d/%d", data->data_read / 4, data->data_size / 4); |             temp_str, "\nPages Read %d/%d", data->data_read / 4, data->data_size / 4); | ||||||
|         if(data->data_size > data->data_read) { |         if(data->data_size > data->data_read) { | ||||||
|             furi_string_cat_printf(temp_str, "\nPassword-protected"); |             furi_string_cat_printf(temp_str, "\nPassword-protected"); | ||||||
|  |         } else if(data->auth_success) { | ||||||
|  |             MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); | ||||||
|  |             furi_string_cat_printf( | ||||||
|  |                 temp_str, | ||||||
|  |                 "\nPassword: %02X %02X %02X %02X", | ||||||
|  |                 config_pages->auth_data.pwd.raw[0], | ||||||
|  |                 config_pages->auth_data.pwd.raw[1], | ||||||
|  |                 config_pages->auth_data.pwd.raw[2], | ||||||
|  |                 config_pages->auth_data.pwd.raw[3]); | ||||||
|  |             furi_string_cat_printf( | ||||||
|  |                 temp_str, | ||||||
|  |                 "\nPACK: %02X %02X", | ||||||
|  |                 config_pages->auth_data.pack.raw[0], | ||||||
|  |                 config_pages->auth_data.pack.raw[1]); | ||||||
|         } |         } | ||||||
|     } else if(protocol == NfcDeviceProtocolMifareClassic) { |     } else if(protocol == NfcDeviceProtocolMifareClassic) { | ||||||
|         MfClassicData* data = &dev_data->mf_classic_data; |         MfClassicData* data = &dev_data->mf_classic_data; | ||||||
| @ -115,7 +129,7 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == GuiButtonTypeRight) { |         if(event.event == GuiButtonTypeRight) { | ||||||
|             if(protocol == NfcDeviceProtocolMifareDesfire) { |             if(protocol == NfcDeviceProtocolMifareDesfire) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData); | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|             } else if(protocol == NfcDeviceProtocolMifareUl) { |             } else if(protocol == NfcDeviceProtocolMifareUl) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); | ||||||
|  | |||||||
| @ -70,6 +70,8 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { | |||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == NfcWorkerEventReadMfUltralight) { |         } else if(event.event == NfcWorkerEventReadMfUltralight) { | ||||||
|             notification_message(nfc->notifications, &sequence_success); |             notification_message(nfc->notifications, &sequence_success); | ||||||
|  |             // Set unlock password input to 0xFFFFFFFF only on fresh read
 | ||||||
|  |             memset(nfc->byte_input_store, 0xFF, 4); | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadSuccess); | ||||||
|             DOLPHIN_DEED(DolphinDeedNfcReadSuccess); |             DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  | |||||||
| @ -11,6 +11,8 @@ enum SubmenuIndex { | |||||||
|     SubmenuIndexDelete, |     SubmenuIndexDelete, | ||||||
|     SubmenuIndexInfo, |     SubmenuIndexInfo, | ||||||
|     SubmenuIndexRestoreOriginal, |     SubmenuIndexRestoreOriginal, | ||||||
|  |     SubmenuIndexMfUlUnlockByReader, | ||||||
|  |     SubmenuIndexMfUlUnlockByPassword, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void nfc_scene_saved_menu_submenu_callback(void* context, uint32_t index) { | void nfc_scene_saved_menu_submenu_callback(void* context, uint32_t index) { | ||||||
| @ -69,6 +71,21 @@ void nfc_scene_saved_menu_on_enter(void* context) { | |||||||
|     } |     } | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); |         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); | ||||||
|  |     if(nfc->dev->format == NfcDeviceSaveFormatMifareUl && | ||||||
|  |        !mf_ul_is_full_capture(&nfc->dev->dev_data.mf_ul_data)) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Unlock With Reader", | ||||||
|  |             SubmenuIndexMfUlUnlockByReader, | ||||||
|  |             nfc_scene_saved_menu_submenu_callback, | ||||||
|  |             nfc); | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Unlock With Password", | ||||||
|  |             SubmenuIndexMfUlUnlockByPassword, | ||||||
|  |             nfc_scene_saved_menu_submenu_callback, | ||||||
|  |             nfc); | ||||||
|  |     } | ||||||
|     if(nfc->dev->shadow_file_exist) { |     if(nfc->dev->shadow_file_exist) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, |             submenu, | ||||||
| @ -106,6 +123,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexDetectReader) { |         } else if(event.event == SubmenuIndexDetectReader) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); | ||||||
|  |             DOLPHIN_DEED(DolphinDeedNfcDetectReader); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexWrite) { |         } else if(event.event == SubmenuIndexWrite) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); | ||||||
| @ -141,6 +159,12 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|         } else if(event.event == SubmenuIndexRestoreOriginal) { |         } else if(event.event == SubmenuIndexRestoreOriginal) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginalConfirm); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginalConfirm); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexMfUlUnlockByReader) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexMfUlUnlockByPassword) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -59,11 +59,8 @@ SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) { | |||||||
| 
 | 
 | ||||||
|     instance->cli = cli; |     instance->cli = cli; | ||||||
| 
 | 
 | ||||||
|     instance->thread = furi_thread_alloc(); |     instance->thread = | ||||||
|     furi_thread_set_name(instance->thread, "SubGhzChat"); |         furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); | ||||||
|     furi_thread_set_stack_size(instance->thread, 2048); |  | ||||||
|     furi_thread_set_context(instance->thread, instance); |  | ||||||
|     furi_thread_set_callback(instance->thread, subghz_chat_worker_thread); |  | ||||||
|     instance->subghz_txrx = subghz_tx_rx_worker_alloc(); |     instance->subghz_txrx = subghz_tx_rx_worker_alloc(); | ||||||
|     instance->event_queue = furi_message_queue_alloc(80, sizeof(SubGhzChatEvent)); |     instance->event_queue = furi_message_queue_alloc(80, sizeof(SubGhzChatEvent)); | ||||||
|     return instance; |     return instance; | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ TUPLE_DEF2( | |||||||
|     (frequency, uint32_t), |     (frequency, uint32_t), | ||||||
|     (count, uint8_t), |     (count, uint8_t), | ||||||
|     (rssi_max, uint8_t)) |     (rssi_max, uint8_t)) | ||||||
| /* Register globaly the oplist */ | /* Register globally the oplist */ | ||||||
| #define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ | #define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ | ||||||
|     TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) |     TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,8 +5,6 @@ | |||||||
| 
 | 
 | ||||||
| #define TAG "SubghzFrequencyAnalyzerWorker" | #define TAG "SubghzFrequencyAnalyzerWorker" | ||||||
| 
 | 
 | ||||||
| #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -95.0f |  | ||||||
| 
 |  | ||||||
| static const uint8_t subghz_preset_ook_58khz[][2] = { | static const uint8_t subghz_preset_ook_58khz[][2] = { | ||||||
|     {CC1101_MDMCFG4, 0b11110111}, // Rx BW filter is 58.035714kHz
 |     {CC1101_MDMCFG4, 0b11110111}, // Rx BW filter is 58.035714kHz
 | ||||||
|     /* End  */ |     /* End  */ | ||||||
| @ -71,7 +69,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | |||||||
|         .frequency_coarse = 0, .rssi_coarse = 0, .frequency_fine = 0, .rssi_fine = 0}; |         .frequency_coarse = 0, .rssi_coarse = 0, .frequency_fine = 0, .rssi_fine = 0}; | ||||||
|     float rssi = 0; |     float rssi = 0; | ||||||
|     uint32_t frequency = 0; |     uint32_t frequency = 0; | ||||||
|     float rssi_temp = 0; |     float rssi_temp = -127.0f; | ||||||
|     uint32_t frequency_temp = 0; |     uint32_t frequency_temp = 0; | ||||||
|     CC1101Status status; |     CC1101Status status; | ||||||
| 
 | 
 | ||||||
| @ -196,7 +194,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | |||||||
|                 TAG, "=:%lu:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); |                 TAG, "=:%lu:%f", frequency_rssi.frequency_fine, (double)frequency_rssi.rssi_fine); | ||||||
| 
 | 
 | ||||||
|             instance->sample_hold_counter = 20; |             instance->sample_hold_counter = 20; | ||||||
|             rssi_temp = frequency_rssi.rssi_fine; |             rssi_temp = (rssi_temp + frequency_rssi.rssi_fine) / 2; | ||||||
|             frequency_temp = frequency_rssi.frequency_fine; |             frequency_temp = frequency_rssi.frequency_fine; | ||||||
| 
 | 
 | ||||||
|             if(instance->filVal) { |             if(instance->filVal) { | ||||||
| @ -207,10 +205,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | |||||||
|             // Deliver callback
 |             // Deliver callback
 | ||||||
|             if(instance->pair_callback) { |             if(instance->pair_callback) { | ||||||
|                 instance->pair_callback( |                 instance->pair_callback( | ||||||
|                     instance->context, |                     instance->context, frequency_rssi.frequency_fine, rssi_temp, true); | ||||||
|                     frequency_rssi.frequency_fine, |  | ||||||
|                     frequency_rssi.rssi_fine, |  | ||||||
|                     true); |  | ||||||
|             } |             } | ||||||
|         } else if( // Deliver results coarse
 |         } else if( // Deliver results coarse
 | ||||||
|             (frequency_rssi.rssi_coarse > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) && |             (frequency_rssi.rssi_coarse > SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) && | ||||||
| @ -222,7 +217,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | |||||||
|                 (double)frequency_rssi.rssi_coarse); |                 (double)frequency_rssi.rssi_coarse); | ||||||
| 
 | 
 | ||||||
|             instance->sample_hold_counter = 20; |             instance->sample_hold_counter = 20; | ||||||
|             rssi_temp = frequency_rssi.rssi_coarse; |             rssi_temp = (rssi_temp + frequency_rssi.rssi_coarse) / 2; | ||||||
|             frequency_temp = frequency_rssi.frequency_coarse; |             frequency_temp = frequency_rssi.frequency_coarse; | ||||||
|             if(instance->filVal) { |             if(instance->filVal) { | ||||||
|                 frequency_rssi.frequency_coarse = |                 frequency_rssi.frequency_coarse = | ||||||
| @ -232,15 +227,12 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | |||||||
|             // Deliver callback
 |             // Deliver callback
 | ||||||
|             if(instance->pair_callback) { |             if(instance->pair_callback) { | ||||||
|                 instance->pair_callback( |                 instance->pair_callback( | ||||||
|                     instance->context, |                     instance->context, frequency_rssi.frequency_coarse, rssi_temp, true); | ||||||
|                     frequency_rssi.frequency_coarse, |  | ||||||
|                     frequency_rssi.rssi_coarse, |  | ||||||
|                     true); |  | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             if(instance->sample_hold_counter > 0) { |             if(instance->sample_hold_counter > 0) { | ||||||
|                 instance->sample_hold_counter--; |                 instance->sample_hold_counter--; | ||||||
|                 if(instance->sample_hold_counter == 18) { |                 if(instance->sample_hold_counter == 15) { | ||||||
|                     if(instance->pair_callback) { |                     if(instance->pair_callback) { | ||||||
|                         instance->pair_callback( |                         instance->pair_callback( | ||||||
|                             instance->context, frequency_temp, rssi_temp, false); |                             instance->context, frequency_temp, rssi_temp, false); | ||||||
| @ -248,8 +240,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | |||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 instance->filVal = 0; |                 instance->filVal = 0; | ||||||
|                 if(instance->pair_callback) |                 rssi_temp = -127.0f; | ||||||
|                     instance->pair_callback(instance->context, 0, 0, false); |                 instance->pair_callback(instance->context, 0, 0, false); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -265,12 +257,8 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); |     SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); | ||||||
| 
 | 
 | ||||||
|     instance->thread = furi_thread_alloc(); |     instance->thread = furi_thread_alloc_ex( | ||||||
|     furi_thread_set_name(instance->thread, "SubGhzFAWorker"); |         "SubGhzFAWorker", 2048, subghz_frequency_analyzer_worker_thread, instance); | ||||||
|     furi_thread_set_stack_size(instance->thread, 2048); |  | ||||||
|     furi_thread_set_context(instance->thread, instance); |  | ||||||
|     furi_thread_set_callback(instance->thread, subghz_frequency_analyzer_worker_thread); |  | ||||||
| 
 |  | ||||||
|     SubGhz* subghz = context; |     SubGhz* subghz = context; | ||||||
|     instance->setting = subghz->setting; |     instance->setting = subghz->setting; | ||||||
|     return instance; |     return instance; | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ | |||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include "../subghz_i.h" | #include "../subghz_i.h" | ||||||
| 
 | 
 | ||||||
|  | #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -93.0f | ||||||
|  | 
 | ||||||
| typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; | typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; | ||||||
| 
 | 
 | ||||||
| typedef void (*SubGhzFrequencyAnalyzerWorkerPairCallback)( | typedef void (*SubGhzFrequencyAnalyzerWorkerPairCallback)( | ||||||
|  | |||||||
| @ -300,7 +300,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { | |||||||
| 
 | 
 | ||||||
|     furi_hal_power_suppress_charge_exit(); |     furi_hal_power_suppress_charge_exit(); | ||||||
| 
 | 
 | ||||||
|     printf("\r\nPackets recieved %u\r\n", instance->packet_count); |     printf("\r\nPackets received %u\r\n", instance->packet_count); | ||||||
| 
 | 
 | ||||||
|     // Cleanup
 |     // Cleanup
 | ||||||
|     subghz_receiver_free(receiver); |     subghz_receiver_free(receiver); | ||||||
| @ -408,7 +408,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         printf("\r\nPackets recieved \033[0;32m%u\033[0m\r\n", instance->packet_count); |         printf("\r\nPackets received \033[0;32m%u\033[0m\r\n", instance->packet_count); | ||||||
| 
 | 
 | ||||||
|         // Cleanup
 |         // Cleanup
 | ||||||
|         subghz_receiver_free(receiver); |         subghz_receiver_free(receiver); | ||||||
| @ -438,7 +438,7 @@ static void subghz_cli_command_print_usage() { | |||||||
|         printf("\r\n"); |         printf("\r\n"); | ||||||
|         printf("  debug cmd:\r\n"); |         printf("  debug cmd:\r\n"); | ||||||
|         printf("\ttx_carrier <frequency:in Hz>\t - Transmit carrier\r\n"); |         printf("\ttx_carrier <frequency:in Hz>\t - Transmit carrier\r\n"); | ||||||
|         printf("\trx_carrier <frequency:in Hz>\t - Receiv carrier\r\n"); |         printf("\trx_carrier <frequency:in Hz>\t - Receive carrier\r\n"); | ||||||
|         printf( |         printf( | ||||||
|             "\tencrypt_keeloq <path_decrypted_file> <path_encrypted_file> <IV:16 bytes in hex>\t - Encrypt keeloq manufacture keys\r\n"); |             "\tencrypt_keeloq <path_decrypted_file> <path_encrypted_file> <IV:16 bytes in hex>\t - Encrypt keeloq manufacture keys\r\n"); | ||||||
|         printf( |         printf( | ||||||
|  | |||||||
| @ -13,8 +13,6 @@ | |||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 | 
 | ||||||
| #define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem)
 | #define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem)
 | ||||||
| #define RSSI_OFFSET 74 |  | ||||||
| #define RSSI_MAX 53 // 127 - RSSI_OFFSET
 |  | ||||||
| 
 | 
 | ||||||
| #define SNPRINTF_FREQUENCY(buff, freq) \ | #define SNPRINTF_FREQUENCY(buff, freq) \ | ||||||
|     snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000); |     snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000); | ||||||
| @ -49,7 +47,7 @@ typedef struct { | |||||||
| } SubGhzFrequencyAnalyzerModel; | } SubGhzFrequencyAnalyzerModel; | ||||||
| 
 | 
 | ||||||
| static inline uint8_t rssi_sanitize(float rssi) { | static inline uint8_t rssi_sanitize(float rssi) { | ||||||
|     return (rssi * -1.0f) - RSSI_OFFSET; |     return (rssi ? (uint8_t)(rssi - SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD) : 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void subghz_frequency_analyzer_set_callback( | void subghz_frequency_analyzer_set_callback( | ||||||
| @ -65,12 +63,25 @@ void subghz_frequency_analyzer_set_callback( | |||||||
| void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { | void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { | ||||||
|     uint8_t column_number = 0; |     uint8_t column_number = 0; | ||||||
|     if(rssi) { |     if(rssi) { | ||||||
|         rssi = rssi / 3; |         rssi = rssi / 3 + 2; | ||||||
|  |         if(rssi > 20) rssi = 20; | ||||||
|         for(uint8_t i = 1; i < rssi; i++) { |         for(uint8_t i = 1; i < rssi; i++) { | ||||||
|             if(i > 20) break; |  | ||||||
|             if(i % 4) { |             if(i % 4) { | ||||||
|                 column_number++; |                 column_number++; | ||||||
|                 canvas_draw_box(canvas, x + 2 * i, y - column_number, 2, 4 + column_number); |                 canvas_draw_box(canvas, x + 2 * i, y - column_number, 2, column_number); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void subghz_frequency_analyzer_draw_log_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { | ||||||
|  |     uint8_t column_height = 6; | ||||||
|  |     if(rssi) { | ||||||
|  |         //rssi = rssi
 | ||||||
|  |         if(rssi > 54) rssi = 54; | ||||||
|  |         for(uint8_t i = 1; i < rssi; i++) { | ||||||
|  |             if(i % 5) { | ||||||
|  |                 canvas_draw_box(canvas, x + i, y - column_height, 1, column_height); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -86,9 +97,9 @@ static void subghz_frequency_analyzer_log_frequency_draw( | |||||||
| 
 | 
 | ||||||
|     const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); |     const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); | ||||||
|     if(items_count == 0) { |     if(items_count == 0) { | ||||||
|         canvas_draw_rframe(canvas, offset_x + 27u, offset_y - 3u, 73u, 16u, 5u); |         canvas_draw_rframe(canvas, offset_x + 27, offset_y - 3, 73, 16, 5); | ||||||
|         canvas_draw_str_aligned( |         canvas_draw_str_aligned( | ||||||
|             canvas, offset_x + 64u, offset_y + 8u, AlignCenter, AlignBottom, "No records"); |             canvas, offset_x + 64, offset_y + 8, AlignCenter, AlignBottom, "No records"); | ||||||
|         return; |         return; | ||||||
|     } else if(items_count > 3) { |     } else if(items_count > 3) { | ||||||
|         elements_scrollbar_pos( |         elements_scrollbar_pos( | ||||||
| @ -117,7 +128,7 @@ static void subghz_frequency_analyzer_log_frequency_draw( | |||||||
|         canvas_draw_str(canvas, offset_x + 48, offset_y + i * 10, buffer); |         canvas_draw_str(canvas, offset_x + 48, offset_y + i * 10, buffer); | ||||||
| 
 | 
 | ||||||
|         // Max RSSI
 |         // Max RSSI
 | ||||||
|         subghz_frequency_analyzer_draw_rssi( |         subghz_frequency_analyzer_draw_log_rssi( | ||||||
|             canvas, (*log_frequency_item)->rssi_max, offset_x + 69, (offset_y + i * 10)); |             canvas, (*log_frequency_item)->rssi_max, offset_x + 69, (offset_y + i * 10)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -167,25 +178,20 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel | |||||||
|     } else { |     } else { | ||||||
|         canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); |         canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); | ||||||
|         canvas_draw_str(canvas, 0, 64, "RSSI"); |         canvas_draw_str(canvas, 0, 64, "RSSI"); | ||||||
|         subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20u, 64u); |         subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20, 64); | ||||||
| 
 | 
 | ||||||
|         subghz_frequency_analyzer_history_frequency_draw(canvas, model); |         subghz_frequency_analyzer_history_frequency_draw(canvas, model); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Frequency
 |     // Frequency
 | ||||||
|     canvas_set_font(canvas, FontBigNumbers); |     canvas_set_font(canvas, FontBigNumbers); | ||||||
|     snprintf( |     SNPRINTF_FREQUENCY(buffer, model->frequency); | ||||||
|         buffer, |  | ||||||
|         sizeof(buffer), |  | ||||||
|         "%03ld.%03ld", |  | ||||||
|         model->frequency / 1000000 % 1000, |  | ||||||
|         model->frequency / 1000 % 1000); |  | ||||||
|     if(model->signal) { |     if(model->signal) { | ||||||
|         canvas_draw_box(canvas, 4, 12, 121, 22); |         canvas_draw_box(canvas, 4, 11, 121, 22); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_str(canvas, 8, 30, buffer); |     canvas_draw_str(canvas, 8, 29, buffer); | ||||||
|     canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); |     canvas_draw_icon(canvas, 96, 18, &I_MHz_25x11); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) { | static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) { | ||||||
| @ -292,7 +298,7 @@ static bool subghz_frequency_analyzer_log_frequency_insert(SubGhzFrequencyAnalyz | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         (*item)->frequency = model->frequency; |         (*item)->frequency = model->frequency; | ||||||
|         (*item)->count = 1u; |         (*item)->count = 1; | ||||||
|         (*item)->rssi_max = model->rssi; |         (*item)->rssi_max = model->rssi; | ||||||
|         (*item)->seq = items_count; |         (*item)->seq = items_count; | ||||||
|         return true; |         return true; | ||||||
| @ -394,9 +400,9 @@ void subghz_frequency_analyzer_enter(void* context) { | |||||||
|             model->frequency = 0; |             model->frequency = 0; | ||||||
|             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; |             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; | ||||||
|             model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; |             model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; | ||||||
|             model->log_frequency_scroll_offset = 0u; |             model->log_frequency_scroll_offset = 0; | ||||||
|             model->history_frequency[0] = model->history_frequency[1] = |             model->history_frequency[0] = model->history_frequency[1] = | ||||||
|                 model->history_frequency[2] = 0u; |                 model->history_frequency[2] = 0; | ||||||
|             SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency); |             SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency); | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| @ -416,13 +422,13 @@ void subghz_frequency_analyzer_exit(void* context) { | |||||||
|         instance->view, |         instance->view, | ||||||
|         SubGhzFrequencyAnalyzerModel * model, |         SubGhzFrequencyAnalyzerModel * model, | ||||||
|         { |         { | ||||||
|             model->rssi = 0u; |             model->rssi = 0; | ||||||
|             model->frequency = 0; |             model->frequency = 0; | ||||||
|             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; |             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; | ||||||
|             model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; |             model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; | ||||||
|             model->log_frequency_scroll_offset = 0u; |             model->log_frequency_scroll_offset = 0; | ||||||
|             model->history_frequency[0] = model->history_frequency[1] = |             model->history_frequency[0] = model->history_frequency[1] = | ||||||
|                 model->history_frequency[2] = 0u; |                 model->history_frequency[2] = 0; | ||||||
|             SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency); |             SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency); | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
|  | |||||||
| @ -58,13 +58,13 @@ struct U2fHid_packet { | |||||||
| struct U2fHid { | struct U2fHid { | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     FuriTimer* lock_timer; |     FuriTimer* lock_timer; | ||||||
|     struct U2fHid_packet packet; |  | ||||||
|     uint8_t seq_id_last; |     uint8_t seq_id_last; | ||||||
|     uint16_t req_buf_ptr; |     uint16_t req_buf_ptr; | ||||||
|     uint32_t req_len_left; |     uint32_t req_len_left; | ||||||
|     uint32_t lock_cid; |     uint32_t lock_cid; | ||||||
|     bool lock; |     bool lock; | ||||||
|     U2fData* u2f_instance; |     U2fData* u2f_instance; | ||||||
|  |     struct U2fHid_packet packet; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void u2f_hid_event_callback(HidU2fEvent ev, void* context) { | static void u2f_hid_event_callback(HidU2fEvent ev, void* context) { | ||||||
| @ -215,10 +215,21 @@ static int32_t u2f_hid_worker(void* context) { | |||||||
|         } |         } | ||||||
|         if(flags & WorkerEvtRequest) { |         if(flags & WorkerEvtRequest) { | ||||||
|             uint32_t len_cur = furi_hal_hid_u2f_get_request(packet_buf); |             uint32_t len_cur = furi_hal_hid_u2f_get_request(packet_buf); | ||||||
|             if(len_cur > 0) { |             do { | ||||||
|  |                 if(len_cur == 0) { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|                 if((packet_buf[4] & U2F_HID_TYPE_MASK) == U2F_HID_TYPE_INIT) { |                 if((packet_buf[4] & U2F_HID_TYPE_MASK) == U2F_HID_TYPE_INIT) { | ||||||
|  |                     if(len_cur < 7) { | ||||||
|  |                         u2f_hid->req_len_left = 0; | ||||||
|  |                         break; // Wrong chunk len
 | ||||||
|  |                     } | ||||||
|                     // Init packet
 |                     // Init packet
 | ||||||
|                     u2f_hid->packet.len = (packet_buf[5] << 8) | (packet_buf[6]); |                     u2f_hid->packet.len = (packet_buf[5] << 8) | (packet_buf[6]); | ||||||
|  |                     if(u2f_hid->packet.len > U2F_HID_MAX_PAYLOAD_LEN) { | ||||||
|  |                         u2f_hid->req_len_left = 0; | ||||||
|  |                         break; // Wrong packet len
 | ||||||
|  |                     } | ||||||
|                     if(u2f_hid->packet.len > (len_cur - 7)) { |                     if(u2f_hid->packet.len > (len_cur - 7)) { | ||||||
|                         u2f_hid->req_len_left = u2f_hid->packet.len - (len_cur - 7); |                         u2f_hid->req_len_left = u2f_hid->packet.len - (len_cur - 7); | ||||||
|                         len_cur = len_cur - 7; |                         len_cur = len_cur - 7; | ||||||
| @ -232,6 +243,10 @@ static int32_t u2f_hid_worker(void* context) { | |||||||
|                     u2f_hid->req_buf_ptr = len_cur; |                     u2f_hid->req_buf_ptr = len_cur; | ||||||
|                     if(len_cur > 0) memcpy(u2f_hid->packet.payload, &packet_buf[7], len_cur); |                     if(len_cur > 0) memcpy(u2f_hid->packet.payload, &packet_buf[7], len_cur); | ||||||
|                 } else { |                 } else { | ||||||
|  |                     if(len_cur < 5) { | ||||||
|  |                         u2f_hid->req_len_left = 0; | ||||||
|  |                         break; // Wrong chunk len
 | ||||||
|  |                     } | ||||||
|                     // Continuation packet
 |                     // Continuation packet
 | ||||||
|                     if(u2f_hid->req_len_left > 0) { |                     if(u2f_hid->req_len_left > 0) { | ||||||
|                         uint32_t cid_temp = 0; |                         uint32_t cid_temp = 0; | ||||||
| @ -260,7 +275,7 @@ static int32_t u2f_hid_worker(void* context) { | |||||||
|                         u2f_hid_send_error(u2f_hid, U2F_HID_ERR_INVALID_CMD); |                         u2f_hid_send_error(u2f_hid, U2F_HID_ERR_INVALID_CMD); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } while(0); | ||||||
|         } |         } | ||||||
|         if(flags & WorkerEvtUnlock) { |         if(flags & WorkerEvtUnlock) { | ||||||
|             u2f_hid->lock = false; |             u2f_hid->lock = false; | ||||||
| @ -282,11 +297,7 @@ U2fHid* u2f_hid_start(U2fData* u2f_inst) { | |||||||
| 
 | 
 | ||||||
|     u2f_hid->u2f_instance = u2f_inst; |     u2f_hid->u2f_instance = u2f_inst; | ||||||
| 
 | 
 | ||||||
|     u2f_hid->thread = furi_thread_alloc(); |     u2f_hid->thread = furi_thread_alloc_ex("U2fHidWorker", 2048, u2f_hid_worker, u2f_hid); | ||||||
|     furi_thread_set_name(u2f_hid->thread, "U2fHidWorker"); |  | ||||||
|     furi_thread_set_stack_size(u2f_hid->thread, 2048); |  | ||||||
|     furi_thread_set_context(u2f_hid->thread, u2f_hid); |  | ||||||
|     furi_thread_set_callback(u2f_hid->thread, u2f_hid_worker); |  | ||||||
|     furi_thread_start(u2f_hid->thread); |     furi_thread_start(u2f_hid->thread); | ||||||
|     return u2f_hid; |     return u2f_hid; | ||||||
| } | } | ||||||
|  | |||||||
| @ -37,10 +37,10 @@ static void u2f_view_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     } else if(model->display_msg == U2fMsgSuccess) { |     } else if(model->display_msg == U2fMsgSuccess) { | ||||||
|         canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31); |         canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31); | ||||||
|         canvas_draw_str_aligned( |         canvas_draw_str_aligned( | ||||||
|             canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successfull!"); |             canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successful!"); | ||||||
|     } else if(model->display_msg == U2fMsgError) { |     } else if(model->display_msg == U2fMsgError) { | ||||||
|         canvas_draw_icon(canvas, 22, 15, &I_Error_62x31); |         canvas_draw_icon(canvas, 22, 15, &I_Error_62x31); | ||||||
|         canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Ceritficate error"); |         canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Certificate error"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,10 +0,0 @@ | |||||||
| App( |  | ||||||
|     appid="bt_hid", |  | ||||||
|     name="Bluetooth Remote", |  | ||||||
|     apptype=FlipperAppType.EXTERNAL, |  | ||||||
|     entry_point="bt_hid_app", |  | ||||||
|     stack_size=1 * 1024, |  | ||||||
|     fap_category="Tools", |  | ||||||
|     fap_icon="bt_remote_10px.png", |  | ||||||
|     fap_icon_assets="assets", |  | ||||||
| ) |  | ||||||
| @ -1,216 +0,0 @@ | |||||||
| #include "bt_hid.h" |  | ||||||
| #include <furi_hal_bt.h> |  | ||||||
| #include <notification/notification_messages.h> |  | ||||||
| #include <dolphin/dolphin.h> |  | ||||||
| 
 |  | ||||||
| #define TAG "BtHidApp" |  | ||||||
| 
 |  | ||||||
| enum BtDebugSubmenuIndex { |  | ||||||
|     BtHidSubmenuIndexKeynote, |  | ||||||
|     BtHidSubmenuIndexKeyboard, |  | ||||||
|     BtHidSubmenuIndexMedia, |  | ||||||
|     BtHidSubmenuIndexTikTok, |  | ||||||
|     BtHidSubmenuIndexMouse, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void bt_hid_submenu_callback(void* context, uint32_t index) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     BtHid* app = context; |  | ||||||
|     if(index == BtHidSubmenuIndexKeynote) { |  | ||||||
|         app->view_id = BtHidViewKeynote; |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); |  | ||||||
|     } else if(index == BtHidSubmenuIndexKeyboard) { |  | ||||||
|         app->view_id = BtHidViewKeyboard; |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard); |  | ||||||
|     } else if(index == BtHidSubmenuIndexMedia) { |  | ||||||
|         app->view_id = BtHidViewMedia; |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia); |  | ||||||
|     } else if(index == BtHidSubmenuIndexMouse) { |  | ||||||
|         app->view_id = BtHidViewMouse; |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse); |  | ||||||
|     } else if(index == BtHidSubmenuIndexTikTok) { |  | ||||||
|         app->view_id = BtHidViewTikTok; |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void bt_hid_dialog_callback(DialogExResult result, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     BtHid* app = context; |  | ||||||
|     if(result == DialogExResultLeft) { |  | ||||||
|         view_dispatcher_stop(app->view_dispatcher); |  | ||||||
|     } else if(result == DialogExResultRight) { |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
 |  | ||||||
|     } else if(result == DialogExResultCenter) { |  | ||||||
|         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint32_t bt_hid_exit_confirm_view(void* context) { |  | ||||||
|     UNUSED(context); |  | ||||||
|     return BtHidViewExitConfirm; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint32_t bt_hid_exit(void* context) { |  | ||||||
|     UNUSED(context); |  | ||||||
|     return VIEW_NONE; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     BtHid* bt_hid = context; |  | ||||||
|     bool connected = (status == BtStatusConnected); |  | ||||||
|     if(connected) { |  | ||||||
|         notification_internal_message(bt_hid->notifications, &sequence_set_blue_255); |  | ||||||
|     } else { |  | ||||||
|         notification_internal_message(bt_hid->notifications, &sequence_reset_blue); |  | ||||||
|     } |  | ||||||
|     bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected); |  | ||||||
|     bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected); |  | ||||||
|     bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected); |  | ||||||
|     bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected); |  | ||||||
|     bt_hid_tiktok_set_connected_status(bt_hid->bt_hid_tiktok, connected); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| BtHid* bt_hid_app_alloc() { |  | ||||||
|     BtHid* app = malloc(sizeof(BtHid)); |  | ||||||
| 
 |  | ||||||
|     // Gui
 |  | ||||||
|     app->gui = furi_record_open(RECORD_GUI); |  | ||||||
| 
 |  | ||||||
|     // Bt
 |  | ||||||
|     app->bt = furi_record_open(RECORD_BT); |  | ||||||
| 
 |  | ||||||
|     // Notifications
 |  | ||||||
|     app->notifications = furi_record_open(RECORD_NOTIFICATION); |  | ||||||
| 
 |  | ||||||
|     // 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); |  | ||||||
| 
 |  | ||||||
|     // Submenu view
 |  | ||||||
|     app->submenu = submenu_alloc(); |  | ||||||
|     submenu_add_item( |  | ||||||
|         app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); |  | ||||||
|     submenu_add_item( |  | ||||||
|         app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); |  | ||||||
|     submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); |  | ||||||
|     submenu_add_item( |  | ||||||
|         app->submenu, "TikTok Controller", BtHidSubmenuIndexTikTok, bt_hid_submenu_callback, app); |  | ||||||
|     submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); |  | ||||||
|     view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu)); |  | ||||||
| 
 |  | ||||||
|     // Dialog view
 |  | ||||||
|     app->dialog = dialog_ex_alloc(); |  | ||||||
|     dialog_ex_set_result_callback(app->dialog, bt_hid_dialog_callback); |  | ||||||
|     dialog_ex_set_context(app->dialog, app); |  | ||||||
|     dialog_ex_set_left_button_text(app->dialog, "Exit"); |  | ||||||
|     dialog_ex_set_right_button_text(app->dialog, "Stay"); |  | ||||||
|     dialog_ex_set_center_button_text(app->dialog, "Menu"); |  | ||||||
|     dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog)); |  | ||||||
| 
 |  | ||||||
|     // Keynote view
 |  | ||||||
|     app->bt_hid_keynote = bt_hid_keynote_alloc(); |  | ||||||
|     view_set_previous_callback( |  | ||||||
|         bt_hid_keynote_get_view(app->bt_hid_keynote), bt_hid_exit_confirm_view); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote)); |  | ||||||
| 
 |  | ||||||
|     // Keyboard view
 |  | ||||||
|     app->bt_hid_keyboard = bt_hid_keyboard_alloc(); |  | ||||||
|     view_set_previous_callback( |  | ||||||
|         bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard)); |  | ||||||
| 
 |  | ||||||
|     // Media view
 |  | ||||||
|     app->bt_hid_media = bt_hid_media_alloc(); |  | ||||||
|     view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media)); |  | ||||||
| 
 |  | ||||||
|     // TikTok view
 |  | ||||||
|     app->bt_hid_tiktok = bt_hid_tiktok_alloc(); |  | ||||||
|     view_set_previous_callback( |  | ||||||
|         bt_hid_tiktok_get_view(app->bt_hid_tiktok), bt_hid_exit_confirm_view); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewTikTok, bt_hid_tiktok_get_view(app->bt_hid_tiktok)); |  | ||||||
| 
 |  | ||||||
|     // Mouse view
 |  | ||||||
|     app->bt_hid_mouse = bt_hid_mouse_alloc(); |  | ||||||
|     view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); |  | ||||||
| 
 |  | ||||||
|     // TODO switch to menu after Media is done
 |  | ||||||
|     app->view_id = BtHidViewSubmenu; |  | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); |  | ||||||
| 
 |  | ||||||
|     return app; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void bt_hid_app_free(BtHid* app) { |  | ||||||
|     furi_assert(app); |  | ||||||
| 
 |  | ||||||
|     // Reset notification
 |  | ||||||
|     notification_internal_message(app->notifications, &sequence_reset_blue); |  | ||||||
| 
 |  | ||||||
|     // Free views
 |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewSubmenu); |  | ||||||
|     submenu_free(app->submenu); |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewExitConfirm); |  | ||||||
|     dialog_ex_free(app->dialog); |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote); |  | ||||||
|     bt_hid_keynote_free(app->bt_hid_keynote); |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard); |  | ||||||
|     bt_hid_keyboard_free(app->bt_hid_keyboard); |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia); |  | ||||||
|     bt_hid_media_free(app->bt_hid_media); |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse); |  | ||||||
|     bt_hid_mouse_free(app->bt_hid_mouse); |  | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); |  | ||||||
|     bt_hid_tiktok_free(app->bt_hid_tiktok); |  | ||||||
|     view_dispatcher_free(app->view_dispatcher); |  | ||||||
| 
 |  | ||||||
|     // Close records
 |  | ||||||
|     furi_record_close(RECORD_GUI); |  | ||||||
|     app->gui = NULL; |  | ||||||
|     furi_record_close(RECORD_NOTIFICATION); |  | ||||||
|     app->notifications = NULL; |  | ||||||
|     furi_record_close(RECORD_BT); |  | ||||||
|     app->bt = NULL; |  | ||||||
| 
 |  | ||||||
|     // Free rest
 |  | ||||||
|     free(app); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int32_t bt_hid_app(void* p) { |  | ||||||
|     UNUSED(p); |  | ||||||
|     // Switch profile to Hid
 |  | ||||||
|     BtHid* app = bt_hid_app_alloc(); |  | ||||||
|     bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); |  | ||||||
|     // Change profile
 |  | ||||||
|     if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { |  | ||||||
|         FURI_LOG_E(TAG, "Failed to switch profile"); |  | ||||||
|         bt_hid_app_free(app); |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
|     furi_hal_bt_start_advertising(); |  | ||||||
| 
 |  | ||||||
|     DOLPHIN_DEED(DolphinDeedPluginStart); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_run(app->view_dispatcher); |  | ||||||
| 
 |  | ||||||
|     bt_set_status_changed_callback(app->bt, NULL, NULL); |  | ||||||
|     // Change back profile to Serial
 |  | ||||||
|     bt_set_profile(app->bt, BtProfileSerial); |  | ||||||
| 
 |  | ||||||
|     bt_hid_app_free(app); |  | ||||||
| 
 |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| @ -1,41 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <furi.h> |  | ||||||
| #include <bt/bt_service/bt.h> |  | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <gui/view.h> |  | ||||||
| #include <gui/view_dispatcher.h> |  | ||||||
| #include <notification/notification.h> |  | ||||||
| 
 |  | ||||||
| #include <gui/modules/submenu.h> |  | ||||||
| #include <gui/modules/dialog_ex.h> |  | ||||||
| #include "views/bt_hid_keynote.h" |  | ||||||
| #include "views/bt_hid_keyboard.h" |  | ||||||
| #include "views/bt_hid_media.h" |  | ||||||
| #include "views/bt_hid_mouse.h" |  | ||||||
| #include "views/bt_hid_tiktok.h" |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     Bt* bt; |  | ||||||
|     Gui* gui; |  | ||||||
|     NotificationApp* notifications; |  | ||||||
|     ViewDispatcher* view_dispatcher; |  | ||||||
|     Submenu* submenu; |  | ||||||
|     DialogEx* dialog; |  | ||||||
|     BtHidKeynote* bt_hid_keynote; |  | ||||||
|     BtHidKeyboard* bt_hid_keyboard; |  | ||||||
|     BtHidMedia* bt_hid_media; |  | ||||||
|     BtHidMouse* bt_hid_mouse; |  | ||||||
|     BtHidTikTok* bt_hid_tiktok; |  | ||||||
|     uint32_t view_id; |  | ||||||
| } BtHid; |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     BtHidViewSubmenu, |  | ||||||
|     BtHidViewKeynote, |  | ||||||
|     BtHidViewKeyboard, |  | ||||||
|     BtHidViewMedia, |  | ||||||
|     BtHidViewMouse, |  | ||||||
|     BtHidViewTikTok, |  | ||||||
|     BtHidViewExitConfirm, |  | ||||||
| } BtHidView; |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| typedef struct BtHidKeyboard BtHidKeyboard; |  | ||||||
| 
 |  | ||||||
| BtHidKeyboard* bt_hid_keyboard_alloc(); |  | ||||||
| 
 |  | ||||||
| void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard); |  | ||||||
| 
 |  | ||||||
| View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard); |  | ||||||
| 
 |  | ||||||
| void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected); |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| typedef struct BtHidKeynote BtHidKeynote; |  | ||||||
| 
 |  | ||||||
| BtHidKeynote* bt_hid_keynote_alloc(); |  | ||||||
| 
 |  | ||||||
| void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote); |  | ||||||
| 
 |  | ||||||
| View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote); |  | ||||||
| 
 |  | ||||||
| void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected); |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| typedef struct BtHidMedia BtHidMedia; |  | ||||||
| 
 |  | ||||||
| BtHidMedia* bt_hid_media_alloc(); |  | ||||||
| 
 |  | ||||||
| void bt_hid_media_free(BtHidMedia* bt_hid_media); |  | ||||||
| 
 |  | ||||||
| View* bt_hid_media_get_view(BtHidMedia* bt_hid_media); |  | ||||||
| 
 |  | ||||||
| void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected); |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| typedef struct BtHidMouse BtHidMouse; |  | ||||||
| 
 |  | ||||||
| BtHidMouse* bt_hid_mouse_alloc(); |  | ||||||
| 
 |  | ||||||
| void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse); |  | ||||||
| 
 |  | ||||||
| View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse); |  | ||||||
| 
 |  | ||||||
| void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected); |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| typedef struct BtHidTikTok BtHidTikTok; |  | ||||||
| 
 |  | ||||||
| BtHidTikTok* bt_hid_tiktok_alloc(); |  | ||||||
| 
 |  | ||||||
| void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok); |  | ||||||
| 
 |  | ||||||
| View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok); |  | ||||||
| 
 |  | ||||||
| void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected); |  | ||||||
| @ -247,7 +247,6 @@ static int32_t dap_process(void* p) { | |||||||
| 
 | 
 | ||||||
|     // deinit usb
 |     // deinit usb
 | ||||||
|     furi_hal_usb_set_config(usb_config_prev, NULL); |     furi_hal_usb_set_config(usb_config_prev, NULL); | ||||||
|     dap_common_wait_for_deinit(); |  | ||||||
|     dap_common_usb_free_name(); |     dap_common_usb_free_name(); | ||||||
|     dap_deinit_gpio(swd_pins_prev); |     dap_deinit_gpio(swd_pins_prev); | ||||||
|     return 0; |     return 0; | ||||||
| @ -441,19 +440,6 @@ static int32_t cdc_process(void* p) { | |||||||
| /******************************* MAIN APP **********************************/ | /******************************* MAIN APP **********************************/ | ||||||
| /***************************************************************************/ | /***************************************************************************/ | ||||||
| 
 | 
 | ||||||
| static FuriThread* furi_thread_alloc_ex( |  | ||||||
|     const char* name, |  | ||||||
|     uint32_t stack_size, |  | ||||||
|     FuriThreadCallback callback, |  | ||||||
|     void* context) { |  | ||||||
|     FuriThread* thread = furi_thread_alloc(); |  | ||||||
|     furi_thread_set_name(thread, name); |  | ||||||
|     furi_thread_set_stack_size(thread, stack_size); |  | ||||||
|     furi_thread_set_callback(thread, callback); |  | ||||||
|     furi_thread_set_context(thread, context); |  | ||||||
|     return thread; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static DapApp* dap_app_alloc() { | static DapApp* dap_app_alloc() { | ||||||
|     DapApp* dap_app = malloc(sizeof(DapApp)); |     DapApp* dap_app = malloc(sizeof(DapApp)); | ||||||
|     dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app); |     dap_app->dap_thread = furi_thread_alloc_ex("DAP Process", 1024, dap_process, dap_app); | ||||||
|  | |||||||
| @ -618,23 +618,12 @@ static void hid_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { | |||||||
|     if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1); |     if(dap_state.semaphore_v2 == NULL) dap_state.semaphore_v2 = furi_semaphore_alloc(1, 1); | ||||||
|     if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1); |     if(dap_state.semaphore_cdc == NULL) dap_state.semaphore_cdc = furi_semaphore_alloc(1, 1); | ||||||
| 
 | 
 | ||||||
|     usb_hid.dev_descr->idVendor = DAP_HID_VID; |  | ||||||
|     usb_hid.dev_descr->idProduct = DAP_HID_PID; |  | ||||||
| 
 |  | ||||||
|     usbd_reg_config(dev, hid_ep_config); |     usbd_reg_config(dev, hid_ep_config); | ||||||
|     usbd_reg_control(dev, hid_control); |     usbd_reg_control(dev, hid_control); | ||||||
| 
 | 
 | ||||||
|     usbd_connect(dev, true); |     usbd_connect(dev, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool deinit_flag = false; |  | ||||||
| 
 |  | ||||||
| void dap_common_wait_for_deinit() { |  | ||||||
|     while(!deinit_flag) { |  | ||||||
|         furi_delay_ms(50); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void hid_deinit(usbd_device* dev) { | static void hid_deinit(usbd_device* dev) { | ||||||
|     dap_state.usb_dev = NULL; |     dap_state.usb_dev = NULL; | ||||||
| 
 | 
 | ||||||
| @ -647,12 +636,6 @@ static void hid_deinit(usbd_device* dev) { | |||||||
| 
 | 
 | ||||||
|     usbd_reg_config(dev, NULL); |     usbd_reg_config(dev, NULL); | ||||||
|     usbd_reg_control(dev, NULL); |     usbd_reg_control(dev, NULL); | ||||||
| 
 |  | ||||||
|     free(usb_hid.str_manuf_descr); |  | ||||||
|     free(usb_hid.str_prod_descr); |  | ||||||
| 
 |  | ||||||
|     FURI_SW_MEMBARRIER(); |  | ||||||
|     deinit_flag = true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void hid_on_wakeup(usbd_device* dev) { | static void hid_on_wakeup(usbd_device* dev) { | ||||||
|  | |||||||
| @ -51,5 +51,3 @@ void dap_common_usb_set_state_callback(DapStateCallback callback); | |||||||
| void dap_common_usb_alloc_name(const char* name); | void dap_common_usb_alloc_name(const char* name); | ||||||
| 
 | 
 | ||||||
| void dap_common_usb_free_name(); | void dap_common_usb_free_name(); | ||||||
| 
 |  | ||||||
| void dap_common_wait_for_deinit(); |  | ||||||
							
								
								
									
										24
									
								
								applications/plugins/hid_app/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | |||||||
|  | App( | ||||||
|  |     appid="hid_usb", | ||||||
|  |     name="USB Remote", | ||||||
|  |     apptype=FlipperAppType.PLUGIN, | ||||||
|  |     entry_point="hid_usb_app", | ||||||
|  |     stack_size=1 * 1024, | ||||||
|  |     fap_category="Tools", | ||||||
|  |     fap_icon="hid_usb_10px.png", | ||||||
|  |     fap_icon_assets="assets", | ||||||
|  |     fap_icon_assets_symbol="hid", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | App( | ||||||
|  |     appid="hid_ble", | ||||||
|  |     name="Bluetooth Remote", | ||||||
|  |     apptype=FlipperAppType.PLUGIN, | ||||||
|  |     entry_point="hid_ble_app", | ||||||
|  |     stack_size=1 * 1024, | ||||||
|  |     fap_category="Tools", | ||||||
|  |     fap_icon="hid_ble_10px.png", | ||||||
|  |     fap_icon_assets="assets", | ||||||
|  |     fap_icon_assets_symbol="hid", | ||||||
|  | ) | ||||||
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 102 B After Width: | Height: | Size: 102 B | 
| Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB | 
| Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 102 B After Width: | Height: | Size: 102 B | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB | 
							
								
								
									
										365
									
								
								applications/plugins/hid_app/hid.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,365 @@ | |||||||
|  | #include "hid.h" | ||||||
|  | #include "views.h" | ||||||
|  | #include <notification/notification_messages.h> | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "HidApp" | ||||||
|  | 
 | ||||||
|  | enum HidDebugSubmenuIndex { | ||||||
|  |     HidSubmenuIndexKeynote, | ||||||
|  |     HidSubmenuIndexKeyboard, | ||||||
|  |     HidSubmenuIndexMedia, | ||||||
|  |     BtHidSubmenuIndexTikTok, | ||||||
|  |     HidSubmenuIndexMouse, | ||||||
|  | }; | ||||||
|  | typedef enum { ConnTypeSubmenuIndexBluetooth, ConnTypeSubmenuIndexUsb } ConnTypeDebugSubmenuIndex; | ||||||
|  | 
 | ||||||
|  | static void hid_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Hid* app = context; | ||||||
|  |     if(index == HidSubmenuIndexKeynote) { | ||||||
|  |         app->view_id = HidViewKeynote; | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); | ||||||
|  |     } else if(index == HidSubmenuIndexKeyboard) { | ||||||
|  |         app->view_id = HidViewKeyboard; | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); | ||||||
|  |     } else if(index == HidSubmenuIndexMedia) { | ||||||
|  |         app->view_id = HidViewMedia; | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); | ||||||
|  |     } else if(index == HidSubmenuIndexMouse) { | ||||||
|  |         app->view_id = HidViewMouse; | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); | ||||||
|  |     } else if(index == BtHidSubmenuIndexTikTok) { | ||||||
|  |         app->view_id = BtHidViewTikTok; | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Hid* hid = context; | ||||||
|  |     bool connected = (status == BtStatusConnected); | ||||||
|  |     if(connected) { | ||||||
|  |         notification_internal_message(hid->notifications, &sequence_set_blue_255); | ||||||
|  |     } else { | ||||||
|  |         notification_internal_message(hid->notifications, &sequence_reset_blue); | ||||||
|  |     } | ||||||
|  |     hid_keynote_set_connected_status(hid->hid_keynote, connected); | ||||||
|  |     hid_keyboard_set_connected_status(hid->hid_keyboard, connected); | ||||||
|  |     hid_media_set_connected_status(hid->hid_media, connected); | ||||||
|  |     hid_mouse_set_connected_status(hid->hid_mouse, connected); | ||||||
|  |     hid_tiktok_set_connected_status(hid->hid_tiktok, connected); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void hid_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Hid* app = context; | ||||||
|  |     if(result == DialogExResultLeft) { | ||||||
|  |         view_dispatcher_stop(app->view_dispatcher); | ||||||
|  |     } else if(result == DialogExResultRight) { | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
 | ||||||
|  |     } else if(result == DialogExResultCenter) { | ||||||
|  |         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint32_t hid_exit_confirm_view(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     return HidViewExitConfirm; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint32_t hid_exit(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     return VIEW_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Hid* hid_alloc(HidTransport transport) { | ||||||
|  |     Hid* app = malloc(sizeof(Hid)); | ||||||
|  |     app->transport = transport; | ||||||
|  | 
 | ||||||
|  |     // Gui
 | ||||||
|  |     app->gui = furi_record_open(RECORD_GUI); | ||||||
|  | 
 | ||||||
|  |     // Bt
 | ||||||
|  |     app->bt = furi_record_open(RECORD_BT); | ||||||
|  | 
 | ||||||
|  |     // Notifications
 | ||||||
|  |     app->notifications = furi_record_open(RECORD_NOTIFICATION); | ||||||
|  | 
 | ||||||
|  |     // 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); | ||||||
|  |     // Device Type Submenu view
 | ||||||
|  |     app->device_type_submenu = submenu_alloc(); | ||||||
|  |     submenu_add_item( | ||||||
|  |         app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); | ||||||
|  |     if(app->transport == HidTransportBle) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             app->device_type_submenu, | ||||||
|  |             "TikTok Controller", | ||||||
|  |             BtHidSubmenuIndexTikTok, | ||||||
|  |             hid_submenu_callback, | ||||||
|  |             app); | ||||||
|  |     } | ||||||
|  |     view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); | ||||||
|  |     app->view_id = HidViewSubmenu; | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Hid* hid_app_alloc_view(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Hid* app = context; | ||||||
|  |     // Dialog view
 | ||||||
|  |     app->dialog = dialog_ex_alloc(); | ||||||
|  |     dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); | ||||||
|  |     dialog_ex_set_context(app->dialog, app); | ||||||
|  |     dialog_ex_set_left_button_text(app->dialog, "Exit"); | ||||||
|  |     dialog_ex_set_right_button_text(app->dialog, "Stay"); | ||||||
|  |     dialog_ex_set_center_button_text(app->dialog, "Menu"); | ||||||
|  |     dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); | ||||||
|  | 
 | ||||||
|  |     // Keynote view
 | ||||||
|  |     app->hid_keynote = hid_keynote_alloc(app); | ||||||
|  |     view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); | ||||||
|  | 
 | ||||||
|  |     // Keyboard view
 | ||||||
|  |     app->hid_keyboard = hid_keyboard_alloc(app); | ||||||
|  |     view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); | ||||||
|  | 
 | ||||||
|  |     // Media view
 | ||||||
|  |     app->hid_media = hid_media_alloc(app); | ||||||
|  |     view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); | ||||||
|  | 
 | ||||||
|  |     // TikTok view
 | ||||||
|  |     app->hid_tiktok = hid_tiktok_alloc(app); | ||||||
|  |     view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); | ||||||
|  | 
 | ||||||
|  |     // Mouse view
 | ||||||
|  |     app->hid_mouse = hid_mouse_alloc(app); | ||||||
|  |     view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_free(Hid* app) { | ||||||
|  |     furi_assert(app); | ||||||
|  | 
 | ||||||
|  |     // Reset notification
 | ||||||
|  |     notification_internal_message(app->notifications, &sequence_reset_blue); | ||||||
|  | 
 | ||||||
|  |     // Free views
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); | ||||||
|  |     submenu_free(app->device_type_submenu); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); | ||||||
|  |     dialog_ex_free(app->dialog); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); | ||||||
|  |     hid_keynote_free(app->hid_keynote); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); | ||||||
|  |     hid_keyboard_free(app->hid_keyboard); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); | ||||||
|  |     hid_media_free(app->hid_media); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); | ||||||
|  |     hid_mouse_free(app->hid_mouse); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); | ||||||
|  |     hid_tiktok_free(app->hid_tiktok); | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     // Close records
 | ||||||
|  |     furi_record_close(RECORD_GUI); | ||||||
|  |     app->gui = NULL; | ||||||
|  |     furi_record_close(RECORD_NOTIFICATION); | ||||||
|  |     app->notifications = NULL; | ||||||
|  |     furi_record_close(RECORD_BT); | ||||||
|  |     app->bt = NULL; | ||||||
|  | 
 | ||||||
|  |     // Free rest
 | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_keyboard_press(Hid* instance, uint16_t event) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_kb_press(event); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_kb_press(event); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_keyboard_release(Hid* instance, uint16_t event) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_kb_release(event); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_kb_release(event); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_keyboard_release_all(Hid* instance) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_kb_release_all(); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_kb_release_all(); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_consumer_key_press(event); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_consumer_key_press(event); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_consumer_key_release(event); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_consumer_key_release(event); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_consumer_key_release_all(Hid* instance) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_consumer_key_release_all(); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_kb_release_all(); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_mouse_move(dx, dy); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_mouse_move(dx, dy); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_mouse_scroll(delta); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_mouse_scroll(delta); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_mouse_press(Hid* instance, uint16_t event) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_mouse_press(event); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_mouse_press(event); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_mouse_release(Hid* instance, uint16_t event) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_mouse_release(event); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_mouse_release(event); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hid_hal_mouse_release_all(Hid* instance) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     if(instance->transport == HidTransportBle) { | ||||||
|  |         furi_hal_bt_hid_mouse_release_all(); | ||||||
|  |     } else if(instance->transport == HidTransportUsb) { | ||||||
|  |         furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); | ||||||
|  |         furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); | ||||||
|  |     } else { | ||||||
|  |         furi_crash(NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t hid_usb_app(void* p) { | ||||||
|  |     UNUSED(p); | ||||||
|  |     Hid* app = hid_alloc(HidTransportUsb); | ||||||
|  |     app = hid_app_alloc_view(app); | ||||||
|  |     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); | ||||||
|  |     furi_hal_usb_unlock(); | ||||||
|  |     furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); | ||||||
|  | 
 | ||||||
|  |     bt_hid_connection_status_changed_callback(BtStatusConnected, app); | ||||||
|  | 
 | ||||||
|  |     DOLPHIN_DEED(DolphinDeedPluginStart); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     furi_hal_usb_set_config(usb_mode_prev, NULL); | ||||||
|  | 
 | ||||||
|  |     hid_free(app); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t hid_ble_app(void* p) { | ||||||
|  |     UNUSED(p); | ||||||
|  |     Hid* app = hid_alloc(HidTransportBle); | ||||||
|  |     app = hid_app_alloc_view(app); | ||||||
|  | 
 | ||||||
|  |     if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { | ||||||
|  |         FURI_LOG_E(TAG, "Failed to switch profile"); | ||||||
|  |     } | ||||||
|  |     furi_hal_bt_start_advertising(); | ||||||
|  |     bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); | ||||||
|  | 
 | ||||||
|  |     DOLPHIN_DEED(DolphinDeedPluginStart); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     bt_set_status_changed_callback(app->bt, NULL, NULL); | ||||||
|  |     bt_set_profile(app->bt, BtProfileSerial); | ||||||
|  | 
 | ||||||
|  |     hid_free(app); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								applications/plugins/hid_app/hid.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,60 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal_bt.h> | ||||||
|  | #include <furi_hal_bt_hid.h> | ||||||
|  | #include <furi_hal_usb.h> | ||||||
|  | #include <furi_hal_usb_hid.h> | ||||||
|  | 
 | ||||||
|  | #include <bt/bt_service/bt.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <notification/notification.h> | ||||||
|  | 
 | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/dialog_ex.h> | ||||||
|  | #include <gui/modules/popup.h> | ||||||
|  | #include "views/hid_keynote.h" | ||||||
|  | #include "views/hid_keyboard.h" | ||||||
|  | #include "views/hid_media.h" | ||||||
|  | #include "views/hid_mouse.h" | ||||||
|  | #include "views/hid_tiktok.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     HidTransportUsb, | ||||||
|  |     HidTransportBle, | ||||||
|  | } HidTransport; | ||||||
|  | 
 | ||||||
|  | typedef struct Hid Hid; | ||||||
|  | 
 | ||||||
|  | struct Hid { | ||||||
|  |     Bt* bt; | ||||||
|  |     Gui* gui; | ||||||
|  |     NotificationApp* notifications; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     Submenu* device_type_submenu; | ||||||
|  |     DialogEx* dialog; | ||||||
|  |     HidKeynote* hid_keynote; | ||||||
|  |     HidKeyboard* hid_keyboard; | ||||||
|  |     HidMedia* hid_media; | ||||||
|  |     HidMouse* hid_mouse; | ||||||
|  |     HidTikTok* hid_tiktok; | ||||||
|  | 
 | ||||||
|  |     HidTransport transport; | ||||||
|  |     uint32_t view_id; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void hid_hal_keyboard_press(Hid* instance, uint16_t event); | ||||||
|  | void hid_hal_keyboard_release(Hid* instance, uint16_t event); | ||||||
|  | void hid_hal_keyboard_release_all(Hid* instance); | ||||||
|  | 
 | ||||||
|  | void hid_hal_consumer_key_press(Hid* instance, uint16_t event); | ||||||
|  | void hid_hal_consumer_key_release(Hid* instance, uint16_t event); | ||||||
|  | void hid_hal_consumer_key_release_all(Hid* instance); | ||||||
|  | 
 | ||||||
|  | void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); | ||||||
|  | void hid_hal_mouse_scroll(Hid* instance, int8_t delta); | ||||||
|  | void hid_hal_mouse_press(Hid* instance, uint16_t event); | ||||||
|  | void hid_hal_mouse_release(Hid* instance, uint16_t event); | ||||||
|  | void hid_hal_mouse_release_all(Hid* instance); | ||||||
| Before Width: | Height: | Size: 151 B After Width: | Height: | Size: 151 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/hid_usb_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 969 B | 
							
								
								
									
										9
									
								
								applications/plugins/hid_app/views.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,9 @@ | |||||||
|  | typedef enum { | ||||||
|  |     HidViewSubmenu, | ||||||
|  |     HidViewKeynote, | ||||||
|  |     HidViewKeyboard, | ||||||
|  |     HidViewMedia, | ||||||
|  |     HidViewMouse, | ||||||
|  |     BtHidViewTikTok, | ||||||
|  |     HidViewExitConfirm, | ||||||
|  | } HidView; | ||||||
| @ -1,14 +1,15 @@ | |||||||
| #include "bt_hid_keyboard.h" | #include "hid_keyboard.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal_bt_hid.h> |  | ||||||
| #include <furi_hal_usb_hid.h> |  | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <gui/icon_i.h> | #include <gui/icon_i.h> | ||||||
|  | #include "../hid.h" | ||||||
|  | #include "hid_icons.h" | ||||||
| 
 | 
 | ||||||
| #include "bt_hid_icons.h" | #define TAG "HidKeyboard" | ||||||
| 
 | 
 | ||||||
| struct BtHidKeyboard { | struct HidKeyboard { | ||||||
|     View* view; |     View* view; | ||||||
|  |     Hid* hid; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -24,7 +25,7 @@ typedef struct { | |||||||
|     bool back_pressed; |     bool back_pressed; | ||||||
|     bool connected; |     bool connected; | ||||||
|     char key_string[5]; |     char key_string[5]; | ||||||
| } BtHidKeyboardModel; | } HidKeyboardModel; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t width; |     uint8_t width; | ||||||
| @ -32,13 +33,12 @@ typedef struct { | |||||||
|     const Icon* icon; |     const Icon* icon; | ||||||
|     char* shift_key; |     char* shift_key; | ||||||
|     uint8_t value; |     uint8_t value; | ||||||
| } BtHidKeyboardKey; | } HidKeyboardKey; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     int8_t x; |     int8_t x; | ||||||
|     int8_t y; |     int8_t y; | ||||||
| } BtHidKeyboardPoint; | } HidKeyboardPoint; | ||||||
| 
 |  | ||||||
| // 4 BY 12
 | // 4 BY 12
 | ||||||
| #define MARGIN_TOP 0 | #define MARGIN_TOP 0 | ||||||
| #define MARGIN_LEFT 4 | #define MARGIN_LEFT 4 | ||||||
| @ -49,7 +49,7 @@ typedef struct { | |||||||
| #define COLUMN_COUNT 12 | #define COLUMN_COUNT 12 | ||||||
| 
 | 
 | ||||||
| // 0 width items are not drawn, but there value is used
 | // 0 width items are not drawn, but there value is used
 | ||||||
| const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | ||||||
|     { |     { | ||||||
|         {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, |         {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, | ||||||
|         {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, |         {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, | ||||||
| @ -112,7 +112,7 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, |         {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, | ||||||
|         {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA}, |         {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, | ||||||
|         {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, |         {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, | ||||||
|         {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, |         {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, | ||||||
|         {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, |         {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, | ||||||
| @ -140,19 +140,19 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | |||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keyboard_to_upper(char* str) { | static void hid_keyboard_to_upper(char* str) { | ||||||
|     while(*str) { |     while(*str) { | ||||||
|         *str = toupper((unsigned char)*str); |         *str = toupper((unsigned char)*str); | ||||||
|         str++; |         str++; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keyboard_draw_key( | static void hid_keyboard_draw_key( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
|     BtHidKeyboardModel* model, |     HidKeyboardModel* model, | ||||||
|     uint8_t x, |     uint8_t x, | ||||||
|     uint8_t y, |     uint8_t y, | ||||||
|     BtHidKeyboardKey key, |     HidKeyboardKey key, | ||||||
|     bool selected) { |     bool selected) { | ||||||
|     if(!key.width) return; |     if(!key.width) return; | ||||||
| 
 | 
 | ||||||
| @ -190,7 +190,7 @@ static void bt_hid_keyboard_draw_key( | |||||||
|         if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || |         if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || | ||||||
|            (model->alt && key.value == HID_KEYBOARD_L_ALT) || |            (model->alt && key.value == HID_KEYBOARD_L_ALT) || | ||||||
|            (model->gui && key.value == HID_KEYBOARD_L_GUI)) { |            (model->gui && key.value == HID_KEYBOARD_L_GUI)) { | ||||||
|             bt_hid_keyboard_to_upper(model->key_string); |             hid_keyboard_to_upper(model->key_string); | ||||||
|         } |         } | ||||||
|         canvas_draw_str_aligned( |         canvas_draw_str_aligned( | ||||||
|             canvas, |             canvas, | ||||||
| @ -202,9 +202,9 @@ static void bt_hid_keyboard_draw_key( | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { | static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BtHidKeyboardModel* model = context; |     HidKeyboardModel* model = context; | ||||||
| 
 | 
 | ||||||
|     // Header
 |     // Header
 | ||||||
|     if(!model->connected) { |     if(!model->connected) { | ||||||
| @ -225,17 +225,17 @@ static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { | |||||||
|     // Start shifting the all keys up if on the next row (Scrolling)
 |     // Start shifting the all keys up if on the next row (Scrolling)
 | ||||||
|     uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; |     uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; | ||||||
|     for(uint8_t y = initY; y < ROW_COUNT; y++) { |     for(uint8_t y = initY; y < ROW_COUNT; y++) { | ||||||
|         const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y]; |         const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; | ||||||
|         uint8_t x = 0; |         uint8_t x = 0; | ||||||
|         for(uint8_t i = 0; i < COLUMN_COUNT; i++) { |         for(uint8_t i = 0; i < COLUMN_COUNT; i++) { | ||||||
|             BtHidKeyboardKey key = keyboardKeyRow[i]; |             HidKeyboardKey key = keyboardKeyRow[i]; | ||||||
|             // Select when the button is hovered
 |             // Select when the button is hovered
 | ||||||
|             // Select if the button is hovered within its width
 |             // Select if the button is hovered within its width
 | ||||||
|             // Select if back is clicked and its the backspace key
 |             // Select if back is clicked and its the backspace key
 | ||||||
|             // Deselect when the button clicked or not hovered
 |             // Deselect when the button clicked or not hovered
 | ||||||
|             bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; |             bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; | ||||||
|             bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; |             bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; | ||||||
|             bt_hid_keyboard_draw_key( |             hid_keyboard_draw_key( | ||||||
|                 canvas, |                 canvas, | ||||||
|                 model, |                 model, | ||||||
|                 x, |                 x, | ||||||
| @ -247,8 +247,8 @@ static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) { | static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) { | ||||||
|     BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x]; |     HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x]; | ||||||
|     // Use upper case if shift is toggled
 |     // Use upper case if shift is toggled
 | ||||||
|     bool useUppercase = model->shift; |     bool useUppercase = model->shift; | ||||||
|     // Check if the key has an upper case version
 |     // Check if the key has an upper case version
 | ||||||
| @ -259,34 +259,34 @@ static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) { | |||||||
|         return key.value; |         return key.value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) { | static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) { | ||||||
|     // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
 |     // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
 | ||||||
|     do { |     do { | ||||||
|         if(((int8_t)model->y) + delta.y < 0) |         if(((int8_t)model->y) + delta.y < 0) | ||||||
|             model->y = ROW_COUNT - 1; |             model->y = ROW_COUNT - 1; | ||||||
|         else |         else | ||||||
|             model->y = (model->y + delta.y) % ROW_COUNT; |             model->y = (model->y + delta.y) % ROW_COUNT; | ||||||
|     } while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0); |     } while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(((int8_t)model->x) + delta.x < 0) |         if(((int8_t)model->x) + delta.x < 0) | ||||||
|             model->x = COLUMN_COUNT - 1; |             model->x = COLUMN_COUNT - 1; | ||||||
|         else |         else | ||||||
|             model->x = (model->x + delta.x) % COLUMN_COUNT; |             model->x = (model->x + delta.x) % COLUMN_COUNT; | ||||||
|     } while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width == |     } while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width == | ||||||
|                                 0); // Skip zero width keys, pretend they are one key
 |                                 0); // Skip zero width keys, pretend they are one key
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) { | static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_keyboard->view, |         hid_keyboard->view, | ||||||
|         BtHidKeyboardModel * model, |         HidKeyboardModel * model, | ||||||
|         { |         { | ||||||
|             if(event->key == InputKeyOk) { |             if(event->key == InputKeyOk) { | ||||||
|                 if(event->type == InputTypePress) { |                 if(event->type == InputTypePress) { | ||||||
|                     model->ok_pressed = true; |                     model->ok_pressed = true; | ||||||
|                 } else if(event->type == InputTypeLong || event->type == InputTypeShort) { |                 } else if(event->type == InputTypeLong || event->type == InputTypeShort) { | ||||||
|                     model->last_key_code = bt_hid_keyboard_get_selected_key(model); |                     model->last_key_code = hid_keyboard_get_selected_key(model); | ||||||
| 
 | 
 | ||||||
|                     // Toggle the modifier key when clicked, and click the key
 |                     // Toggle the modifier key when clicked, and click the key
 | ||||||
|                     if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { |                     if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { | ||||||
| @ -314,10 +314,12 @@ static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* | |||||||
|                         else |                         else | ||||||
|                             model->modifier_code &= ~KEY_MOD_LEFT_GUI; |                             model->modifier_code &= ~KEY_MOD_LEFT_GUI; | ||||||
|                     } |                     } | ||||||
|                     furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code); |                     hid_hal_keyboard_press( | ||||||
|  |                         hid_keyboard->hid, model->modifier_code | model->last_key_code); | ||||||
|                 } else if(event->type == InputTypeRelease) { |                 } else if(event->type == InputTypeRelease) { | ||||||
|                     // Release happens after short and long presses
 |                     // Release happens after short and long presses
 | ||||||
|                     furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code); |                     hid_hal_keyboard_release( | ||||||
|  |                         hid_keyboard->hid, model->modifier_code | model->last_key_code); | ||||||
|                     model->ok_pressed = false; |                     model->ok_pressed = false; | ||||||
|                 } |                 } | ||||||
|             } else if(event->key == InputKeyBack) { |             } else if(event->key == InputKeyBack) { | ||||||
| @ -325,66 +327,67 @@ static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* | |||||||
|                 if(event->type == InputTypePress) { |                 if(event->type == InputTypePress) { | ||||||
|                     model->back_pressed = true; |                     model->back_pressed = true; | ||||||
|                 } else if(event->type == InputTypeShort) { |                 } else if(event->type == InputTypeShort) { | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); |                     hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE); | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); |                     hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE); | ||||||
|                 } else if(event->type == InputTypeRelease) { |                 } else if(event->type == InputTypeRelease) { | ||||||
|                     model->back_pressed = false; |                     model->back_pressed = false; | ||||||
|                 } |                 } | ||||||
|             } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { |             } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { | ||||||
|                 // Cycle the selected keys
 |                 // Cycle the selected keys
 | ||||||
|                 if(event->key == InputKeyUp) { |                 if(event->key == InputKeyUp) { | ||||||
|                     bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1}); |                     hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1}); | ||||||
|                 } else if(event->key == InputKeyDown) { |                 } else if(event->key == InputKeyDown) { | ||||||
|                     bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1}); |                     hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1}); | ||||||
|                 } else if(event->key == InputKeyLeft) { |                 } else if(event->key == InputKeyLeft) { | ||||||
|                     bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0}); |                     hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0}); | ||||||
|                 } else if(event->key == InputKeyRight) { |                 } else if(event->key == InputKeyRight) { | ||||||
|                     bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0}); |                     hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0}); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) { | static bool hid_keyboard_input_callback(InputEvent* event, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BtHidKeyboard* bt_hid_keyboard = context; |     HidKeyboard* hid_keyboard = context; | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypeLong && event->key == InputKeyBack) { |     if(event->type == InputTypeLong && event->key == InputKeyBack) { | ||||||
|         furi_hal_bt_hid_kb_release_all(); |         hid_hal_keyboard_release_all(hid_keyboard->hid); | ||||||
|     } else { |     } else { | ||||||
|         bt_hid_keyboard_process(bt_hid_keyboard, event); |         hid_keyboard_process(hid_keyboard, event); | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| BtHidKeyboard* bt_hid_keyboard_alloc() { | HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { | ||||||
|     BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard)); |     HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard)); | ||||||
|     bt_hid_keyboard->view = view_alloc(); |     hid_keyboard->view = view_alloc(); | ||||||
|     view_set_context(bt_hid_keyboard->view, bt_hid_keyboard); |     hid_keyboard->hid = bt_hid; | ||||||
|     view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel)); |     view_set_context(hid_keyboard->view, hid_keyboard); | ||||||
|     view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback); |     view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel)); | ||||||
|     view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback); |     view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback); | ||||||
|  |     view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); | ||||||
| 
 | 
 | ||||||
|     return bt_hid_keyboard; |     return hid_keyboard; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) { | void hid_keyboard_free(HidKeyboard* hid_keyboard) { | ||||||
|     furi_assert(bt_hid_keyboard); |     furi_assert(hid_keyboard); | ||||||
|     view_free(bt_hid_keyboard->view); |     view_free(hid_keyboard->view); | ||||||
|     free(bt_hid_keyboard); |     free(hid_keyboard); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) { | View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) { | ||||||
|     furi_assert(bt_hid_keyboard); |     furi_assert(hid_keyboard); | ||||||
|     return bt_hid_keyboard->view; |     return hid_keyboard->view; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) { | void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) { | ||||||
|     furi_assert(bt_hid_keyboard); |     furi_assert(hid_keyboard); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_keyboard->view, BtHidKeyboardModel * model, { model->connected = connected; }, true); |         hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true); | ||||||
| } | } | ||||||
							
								
								
									
										14
									
								
								applications/plugins/hid_app/views/hid_keyboard.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | typedef struct Hid Hid; | ||||||
|  | typedef struct HidKeyboard HidKeyboard; | ||||||
|  | 
 | ||||||
|  | HidKeyboard* hid_keyboard_alloc(Hid* bt_hid); | ||||||
|  | 
 | ||||||
|  | void hid_keyboard_free(HidKeyboard* hid_keyboard); | ||||||
|  | 
 | ||||||
|  | View* hid_keyboard_get_view(HidKeyboard* hid_keyboard); | ||||||
|  | 
 | ||||||
|  | void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected); | ||||||
| @ -1,13 +1,14 @@ | |||||||
| #include "bt_hid_keynote.h" | #include "hid_keynote.h" | ||||||
| #include <furi.h> |  | ||||||
| #include <furi_hal_bt_hid.h> |  | ||||||
| #include <furi_hal_usb_hid.h> |  | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
|  | #include "../hid.h" | ||||||
| 
 | 
 | ||||||
| #include "bt_hid_icons.h" | #include "hid_icons.h" | ||||||
| 
 | 
 | ||||||
| struct BtHidKeynote { | #define TAG "HidKeynote" | ||||||
|  | 
 | ||||||
|  | struct HidKeynote { | ||||||
|     View* view; |     View* view; | ||||||
|  |     Hid* hid; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -18,9 +19,9 @@ typedef struct { | |||||||
|     bool ok_pressed; |     bool ok_pressed; | ||||||
|     bool back_pressed; |     bool back_pressed; | ||||||
|     bool connected; |     bool connected; | ||||||
| } BtHidKeynoteModel; | } HidKeynoteModel; | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { | static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { | ||||||
|     canvas_draw_triangle(canvas, x, y, 5, 3, dir); |     canvas_draw_triangle(canvas, x, y, 5, 3, dir); | ||||||
|     if(dir == CanvasDirectionBottomToTop) { |     if(dir == CanvasDirectionBottomToTop) { | ||||||
|         canvas_draw_line(canvas, x, y + 6, x, y - 1); |         canvas_draw_line(canvas, x, y + 6, x, y - 1); | ||||||
| @ -33,9 +34,9 @@ static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, Canv | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | static void hid_keynote_draw_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BtHidKeynoteModel* model = context; |     HidKeynoteModel* model = context; | ||||||
| 
 | 
 | ||||||
|     // Header
 |     // Header
 | ||||||
|     if(model->connected) { |     if(model->connected) { | ||||||
| @ -56,7 +57,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | |||||||
|         elements_slightly_rounded_box(canvas, 24, 26, 13, 13); |         elements_slightly_rounded_box(canvas, 24, 26, 13, 13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); |     hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     // Down
 |     // Down
 | ||||||
| @ -65,7 +66,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | |||||||
|         elements_slightly_rounded_box(canvas, 24, 47, 13, 13); |         elements_slightly_rounded_box(canvas, 24, 47, 13, 13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); |     hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     // Left
 |     // Left
 | ||||||
| @ -74,7 +75,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | |||||||
|         elements_slightly_rounded_box(canvas, 3, 47, 13, 13); |         elements_slightly_rounded_box(canvas, 3, 47, 13, 13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); |     hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     // Right
 |     // Right
 | ||||||
| @ -83,7 +84,7 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | |||||||
|         elements_slightly_rounded_box(canvas, 45, 47, 13, 13); |         elements_slightly_rounded_box(canvas, 45, 47, 13, 13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); |     hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     // Ok
 |     // Ok
 | ||||||
| @ -106,100 +107,101 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | |||||||
|     elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); |     elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { | static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_keynote->view, |         hid_keynote->view, | ||||||
|         BtHidKeynoteModel * model, |         HidKeynoteModel * model, | ||||||
|         { |         { | ||||||
|             if(event->type == InputTypePress) { |             if(event->type == InputTypePress) { | ||||||
|                 if(event->key == InputKeyUp) { |                 if(event->key == InputKeyUp) { | ||||||
|                     model->up_pressed = true; |                     model->up_pressed = true; | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW); |                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); | ||||||
|                 } else if(event->key == InputKeyDown) { |                 } else if(event->key == InputKeyDown) { | ||||||
|                     model->down_pressed = true; |                     model->down_pressed = true; | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW); |                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); | ||||||
|                 } else if(event->key == InputKeyLeft) { |                 } else if(event->key == InputKeyLeft) { | ||||||
|                     model->left_pressed = true; |                     model->left_pressed = true; | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW); |                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); | ||||||
|                 } else if(event->key == InputKeyRight) { |                 } else if(event->key == InputKeyRight) { | ||||||
|                     model->right_pressed = true; |                     model->right_pressed = true; | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW); |                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); | ||||||
|                 } else if(event->key == InputKeyOk) { |                 } else if(event->key == InputKeyOk) { | ||||||
|                     model->ok_pressed = true; |                     model->ok_pressed = true; | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR); |                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); | ||||||
|                 } else if(event->key == InputKeyBack) { |                 } else if(event->key == InputKeyBack) { | ||||||
|                     model->back_pressed = true; |                     model->back_pressed = true; | ||||||
|                 } |                 } | ||||||
|             } else if(event->type == InputTypeRelease) { |             } else if(event->type == InputTypeRelease) { | ||||||
|                 if(event->key == InputKeyUp) { |                 if(event->key == InputKeyUp) { | ||||||
|                     model->up_pressed = false; |                     model->up_pressed = false; | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW); |                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW); | ||||||
|                 } else if(event->key == InputKeyDown) { |                 } else if(event->key == InputKeyDown) { | ||||||
|                     model->down_pressed = false; |                     model->down_pressed = false; | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW); |                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW); | ||||||
|                 } else if(event->key == InputKeyLeft) { |                 } else if(event->key == InputKeyLeft) { | ||||||
|                     model->left_pressed = false; |                     model->left_pressed = false; | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW); |                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW); | ||||||
|                 } else if(event->key == InputKeyRight) { |                 } else if(event->key == InputKeyRight) { | ||||||
|                     model->right_pressed = false; |                     model->right_pressed = false; | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW); |                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW); | ||||||
|                 } else if(event->key == InputKeyOk) { |                 } else if(event->key == InputKeyOk) { | ||||||
|                     model->ok_pressed = false; |                     model->ok_pressed = false; | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR); |                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR); | ||||||
|                 } else if(event->key == InputKeyBack) { |                 } else if(event->key == InputKeyBack) { | ||||||
|                     model->back_pressed = false; |                     model->back_pressed = false; | ||||||
|                 } |                 } | ||||||
|             } else if(event->type == InputTypeShort) { |             } else if(event->type == InputTypeShort) { | ||||||
|                 if(event->key == InputKeyBack) { |                 if(event->key == InputKeyBack) { | ||||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); |                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE); | ||||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); |                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE); | ||||||
|                     furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK); |                     hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK); | ||||||
|                     furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK); |                     hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) { | static bool hid_keynote_input_callback(InputEvent* event, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BtHidKeynote* bt_hid_keynote = context; |     HidKeynote* hid_keynote = context; | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypeLong && event->key == InputKeyBack) { |     if(event->type == InputTypeLong && event->key == InputKeyBack) { | ||||||
|         furi_hal_bt_hid_kb_release_all(); |         hid_hal_keyboard_release_all(hid_keynote->hid); | ||||||
|     } else { |     } else { | ||||||
|         bt_hid_keynote_process(bt_hid_keynote, event); |         hid_keynote_process(hid_keynote, event); | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| BtHidKeynote* bt_hid_keynote_alloc() { | HidKeynote* hid_keynote_alloc(Hid* hid) { | ||||||
|     BtHidKeynote* bt_hid_keynote = malloc(sizeof(BtHidKeynote)); |     HidKeynote* hid_keynote = malloc(sizeof(HidKeynote)); | ||||||
|     bt_hid_keynote->view = view_alloc(); |     hid_keynote->view = view_alloc(); | ||||||
|     view_set_context(bt_hid_keynote->view, bt_hid_keynote); |     hid_keynote->hid = hid; | ||||||
|     view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel)); |     view_set_context(hid_keynote->view, hid_keynote); | ||||||
|     view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback); |     view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel)); | ||||||
|     view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback); |     view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); | ||||||
|  |     view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); | ||||||
| 
 | 
 | ||||||
|     return bt_hid_keynote; |     return hid_keynote; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) { | void hid_keynote_free(HidKeynote* hid_keynote) { | ||||||
|     furi_assert(bt_hid_keynote); |     furi_assert(hid_keynote); | ||||||
|     view_free(bt_hid_keynote->view); |     view_free(hid_keynote->view); | ||||||
|     free(bt_hid_keynote); |     free(hid_keynote); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) { | View* hid_keynote_get_view(HidKeynote* hid_keynote) { | ||||||
|     furi_assert(bt_hid_keynote); |     furi_assert(hid_keynote); | ||||||
|     return bt_hid_keynote->view; |     return hid_keynote->view; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) { | void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) { | ||||||
|     furi_assert(bt_hid_keynote); |     furi_assert(hid_keynote); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_keynote->view, BtHidKeynoteModel * model, { model->connected = connected; }, true); |         hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true); | ||||||
| } | } | ||||||
							
								
								
									
										14
									
								
								applications/plugins/hid_app/views/hid_keynote.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | typedef struct Hid Hid; | ||||||
|  | typedef struct HidKeynote HidKeynote; | ||||||
|  | 
 | ||||||
|  | HidKeynote* hid_keynote_alloc(Hid* bt_hid); | ||||||
|  | 
 | ||||||
|  | void hid_keynote_free(HidKeynote* hid_keynote); | ||||||
|  | 
 | ||||||
|  | View* hid_keynote_get_view(HidKeynote* hid_keynote); | ||||||
|  | 
 | ||||||
|  | void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected); | ||||||
| @ -1,13 +1,17 @@ | |||||||
| #include "bt_hid_media.h" | #include "hid_media.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal_bt_hid.h> | #include <furi_hal_bt_hid.h> | ||||||
| #include <furi_hal_usb_hid.h> | #include <furi_hal_usb_hid.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
|  | #include "../hid.h" | ||||||
| 
 | 
 | ||||||
| #include "bt_hid_icons.h" | #include "hid_icons.h" | ||||||
| 
 | 
 | ||||||
| struct BtHidMedia { | #define TAG "HidMedia" | ||||||
|  | 
 | ||||||
|  | struct HidMedia { | ||||||
|     View* view; |     View* view; | ||||||
|  |     Hid* hid; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -17,9 +21,9 @@ typedef struct { | |||||||
|     bool down_pressed; |     bool down_pressed; | ||||||
|     bool ok_pressed; |     bool ok_pressed; | ||||||
|     bool connected; |     bool connected; | ||||||
| } BtHidMediaModel; | } HidMediaModel; | ||||||
| 
 | 
 | ||||||
| static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { | static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { | ||||||
|     canvas_draw_triangle(canvas, x, y, 5, 3, dir); |     canvas_draw_triangle(canvas, x, y, 5, 3, dir); | ||||||
|     if(dir == CanvasDirectionBottomToTop) { |     if(dir == CanvasDirectionBottomToTop) { | ||||||
|         canvas_draw_dot(canvas, x, y - 1); |         canvas_draw_dot(canvas, x, y - 1); | ||||||
| @ -32,9 +36,9 @@ static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, Canvas | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | static void hid_media_draw_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BtHidMediaModel* model = context; |     HidMediaModel* model = context; | ||||||
| 
 | 
 | ||||||
|     // Header
 |     // Header
 | ||||||
|     if(model->connected) { |     if(model->connected) { | ||||||
| @ -76,8 +80,8 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | |||||||
|         canvas_set_bitmap_mode(canvas, 0); |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); |     hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); | ||||||
|     bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); |     hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     // Right
 |     // Right
 | ||||||
| @ -87,8 +91,8 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | |||||||
|         canvas_set_bitmap_mode(canvas, 0); |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); |     hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); | ||||||
|     bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); |     hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     // Ok
 |     // Ok
 | ||||||
| @ -96,7 +100,7 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | |||||||
|         canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); |     hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); | ||||||
|     canvas_draw_line(canvas, 100, 29, 100, 33); |     canvas_draw_line(canvas, 100, 29, 100, 33); | ||||||
|     canvas_draw_line(canvas, 102, 29, 102, 33); |     canvas_draw_line(canvas, 102, 29, 102, 33); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| @ -107,100 +111,101 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | |||||||
|     elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); |     elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) { | static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_media->view, |         hid_media->view, | ||||||
|         BtHidMediaModel * model, |         HidMediaModel * model, | ||||||
|         { |         { | ||||||
|             if(event->key == InputKeyUp) { |             if(event->key == InputKeyUp) { | ||||||
|                 model->up_pressed = true; |                 model->up_pressed = true; | ||||||
|                 furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); |                 hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); | ||||||
|             } else if(event->key == InputKeyDown) { |             } else if(event->key == InputKeyDown) { | ||||||
|                 model->down_pressed = true; |                 model->down_pressed = true; | ||||||
|                 furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); |                 hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); | ||||||
|             } else if(event->key == InputKeyLeft) { |             } else if(event->key == InputKeyLeft) { | ||||||
|                 model->left_pressed = true; |                 model->left_pressed = true; | ||||||
|                 furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK); |                 hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); | ||||||
|             } else if(event->key == InputKeyRight) { |             } else if(event->key == InputKeyRight) { | ||||||
|                 model->right_pressed = true; |                 model->right_pressed = true; | ||||||
|                 furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK); |                 hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); | ||||||
|             } else if(event->key == InputKeyOk) { |             } else if(event->key == InputKeyOk) { | ||||||
|                 model->ok_pressed = true; |                 model->ok_pressed = true; | ||||||
|                 furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE); |                 hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) { | static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_media->view, |         hid_media->view, | ||||||
|         BtHidMediaModel * model, |         HidMediaModel * model, | ||||||
|         { |         { | ||||||
|             if(event->key == InputKeyUp) { |             if(event->key == InputKeyUp) { | ||||||
|                 model->up_pressed = false; |                 model->up_pressed = false; | ||||||
|                 furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); |                 hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT); | ||||||
|             } else if(event->key == InputKeyDown) { |             } else if(event->key == InputKeyDown) { | ||||||
|                 model->down_pressed = false; |                 model->down_pressed = false; | ||||||
|                 furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); |                 hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT); | ||||||
|             } else if(event->key == InputKeyLeft) { |             } else if(event->key == InputKeyLeft) { | ||||||
|                 model->left_pressed = false; |                 model->left_pressed = false; | ||||||
|                 furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK); |                 hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); | ||||||
|             } else if(event->key == InputKeyRight) { |             } else if(event->key == InputKeyRight) { | ||||||
|                 model->right_pressed = false; |                 model->right_pressed = false; | ||||||
|                 furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK); |                 hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK); | ||||||
|             } else if(event->key == InputKeyOk) { |             } else if(event->key == InputKeyOk) { | ||||||
|                 model->ok_pressed = false; |                 model->ok_pressed = false; | ||||||
|                 furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE); |                 hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE); | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool bt_hid_media_input_callback(InputEvent* event, void* context) { | static bool hid_media_input_callback(InputEvent* event, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BtHidMedia* bt_hid_media = context; |     HidMedia* hid_media = context; | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypePress) { |     if(event->type == InputTypePress) { | ||||||
|         bt_hid_media_process_press(bt_hid_media, event); |         hid_media_process_press(hid_media, event); | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } else if(event->type == InputTypeRelease) { |     } else if(event->type == InputTypeRelease) { | ||||||
|         bt_hid_media_process_release(bt_hid_media, event); |         hid_media_process_release(hid_media, event); | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } else if(event->type == InputTypeShort) { |     } else if(event->type == InputTypeShort) { | ||||||
|         if(event->key == InputKeyBack) { |         if(event->key == InputKeyBack) { | ||||||
|             furi_hal_bt_hid_consumer_key_release_all(); |             hid_hal_consumer_key_release_all(hid_media->hid); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| BtHidMedia* bt_hid_media_alloc() { | HidMedia* hid_media_alloc(Hid* hid) { | ||||||
|     BtHidMedia* bt_hid_media = malloc(sizeof(BtHidMedia)); |     HidMedia* hid_media = malloc(sizeof(HidMedia)); | ||||||
|     bt_hid_media->view = view_alloc(); |     hid_media->view = view_alloc(); | ||||||
|     view_set_context(bt_hid_media->view, bt_hid_media); |     hid_media->hid = hid; | ||||||
|     view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel)); |     view_set_context(hid_media->view, hid_media); | ||||||
|     view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback); |     view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel)); | ||||||
|     view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback); |     view_set_draw_callback(hid_media->view, hid_media_draw_callback); | ||||||
|  |     view_set_input_callback(hid_media->view, hid_media_input_callback); | ||||||
| 
 | 
 | ||||||
|     return bt_hid_media; |     return hid_media; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_hid_media_free(BtHidMedia* bt_hid_media) { | void hid_media_free(HidMedia* hid_media) { | ||||||
|     furi_assert(bt_hid_media); |     furi_assert(hid_media); | ||||||
|     view_free(bt_hid_media->view); |     view_free(hid_media->view); | ||||||
|     free(bt_hid_media); |     free(hid_media); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) { | View* hid_media_get_view(HidMedia* hid_media) { | ||||||
|     furi_assert(bt_hid_media); |     furi_assert(hid_media); | ||||||
|     return bt_hid_media->view; |     return hid_media->view; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) { | void hid_media_set_connected_status(HidMedia* hid_media, bool connected) { | ||||||
|     furi_assert(bt_hid_media); |     furi_assert(hid_media); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bt_hid_media->view, BtHidMediaModel * model, { model->connected = connected; }, true); |         hid_media->view, HidMediaModel * model, { model->connected = connected; }, true); | ||||||
| } | } | ||||||
							
								
								
									
										13
									
								
								applications/plugins/hid_app/views/hid_media.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | typedef struct HidMedia HidMedia; | ||||||
|  | 
 | ||||||
|  | HidMedia* hid_media_alloc(); | ||||||
|  | 
 | ||||||
|  | void hid_media_free(HidMedia* hid_media); | ||||||
|  | 
 | ||||||
|  | View* hid_media_get_view(HidMedia* hid_media); | ||||||
|  | 
 | ||||||
|  | void hid_media_set_connected_status(HidMedia* hid_media, bool connected); | ||||||
 Aleksandr Kutuzov
						Aleksandr Kutuzov