Merge remote-tracking branch 'origin/release-candidate' into release
| @ -2,7 +2,7 @@ | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| @ -17,12 +17,12 @@ See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some hel | ||||
| 
 | ||||
| ## 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. | ||||
| - PR that contains code intended to commit crimes is not going to be accepted. | ||||
| - Your PR must comply with our [Coding Style](CODING_STYLE.md) | ||||
| - Your PR must contain code compatiable with project [LICENSE](LICENSE). | ||||
| - Your PR must contain code compatible with project [LICENSE](LICENSE). | ||||
| - PR will only be merged if it pass CI/CD. | ||||
| - PR will only be merged if it pass review by code owner. | ||||
| 
 | ||||
|  | ||||
| @ -71,7 +71,7 @@ void WIEGAND::end() { | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
|     { | ||||
|         _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) { | ||||
|     UNUSED(p); | ||||
|     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); | ||||
|         dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); | ||||
|         return 255; | ||||
|  | ||||
| @ -145,7 +145,7 @@ DisplayTest* display_test_alloc() { | ||||
|     view_set_previous_callback(view, display_test_previous_callback); | ||||
|     view_dispatcher_add_view(instance->view_dispatcher, DisplayTestViewConfigure, view); | ||||
| 
 | ||||
|     // Configurtion items
 | ||||
|     // Configuration items
 | ||||
|     VariableItem* item; | ||||
|     instance->config_bias = false; | ||||
|     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_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); | ||||
| 
 | ||||
|     app->worker_thread = furi_thread_alloc(); | ||||
|     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); | ||||
|     app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); | ||||
|     furi_thread_start(app->worker_thread); | ||||
| 
 | ||||
|     return app; | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
| extern size_t memmgr_get_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,
 | ||||
| // where X = sizeof(void*) + sizeof(size_t), look to BlockLink_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_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)); | ||||
| 
 | ||||
|     // debug allocation info
 | ||||
|  | ||||
| @ -136,7 +136,7 @@ static bool nfc_test_digital_signal_test_encode( | ||||
|             ref_timings_sum += ref[i]; | ||||
|             if(timings_diff > timing_tolerance) { | ||||
|                 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; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
| @ -43,11 +43,8 @@ MU_TEST(storage_file_open_lock) { | ||||
|     File* file = storage_file_alloc(storage); | ||||
| 
 | ||||
|     // file_locker thread start
 | ||||
|     FuriThread* locker_thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(locker_thread, "StorageFileLocker"); | ||||
|     furi_thread_set_stack_size(locker_thread, 2048); | ||||
|     furi_thread_set_context(locker_thread, semaphore); | ||||
|     furi_thread_set_callback(locker_thread, storage_file_locker); | ||||
|     FuriThread* locker_thread = | ||||
|         furi_thread_alloc_ex("StorageFileLocker", 2048, storage_file_locker, semaphore); | ||||
|     furi_thread_start(locker_thread); | ||||
| 
 | ||||
|     // wait for file lock
 | ||||
| @ -133,11 +130,8 @@ MU_TEST(storage_dir_open_lock) { | ||||
|     File* file = storage_file_alloc(storage); | ||||
| 
 | ||||
|     // file_locker thread start
 | ||||
|     FuriThread* locker_thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(locker_thread, "StorageDirLocker"); | ||||
|     furi_thread_set_stack_size(locker_thread, 2048); | ||||
|     furi_thread_set_context(locker_thread, semaphore); | ||||
|     furi_thread_set_callback(locker_thread, storage_dir_locker); | ||||
|     FuriThread* locker_thread = | ||||
|         furi_thread_alloc_ex("StorageDirLocker", 2048, storage_dir_locker, semaphore); | ||||
|     furi_thread_start(locker_thread); | ||||
| 
 | ||||
|     // wait for dir lock
 | ||||
|  | ||||
| @ -219,21 +219,21 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) { | ||||
|     mu_check(stream_eof(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"
 | ||||
|     mu_check(stream_insert_format(stream, "%s%d", "dio", 666)); | ||||
|     mu_assert_int_eq(6, stream_size(stream)); | ||||
|     mu_check(stream_eof(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"
 | ||||
|     mu_check(stream_insert_format(stream, "%s%d", "zlo", 555)); | ||||
|     mu_assert_int_eq(12, stream_size(stream)); | ||||
|     mu_check(stream_eof(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"
 | ||||
|     mu_check(stream_seek(stream, 6, StreamOffsetFromStart)); | ||||
|     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 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_COUNT_PARSE 232 | ||||
| #define TEST_RANDOM_COUNT_PARSE 244 | ||||
| #define TEST_TIMEOUT 10000 | ||||
| 
 | ||||
| static SubGhzEnvironment* environment_handler; | ||||
| @ -209,6 +209,149 @@ MU_TEST(subghz_keystore_test) { | ||||
|         "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
 | ||||
| MU_TEST(subghz_decoder_came_atomo_test) { | ||||
|     mu_assert( | ||||
| @ -437,6 +580,13 @@ MU_TEST(subghz_decoder_clemsa_test) { | ||||
|         "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
 | ||||
| MU_TEST(subghz_encoder_princeton_test) { | ||||
|     mu_assert( | ||||
| @ -558,6 +708,12 @@ MU_TEST(subghz_encoder_clemsa_test) { | ||||
|         "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_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(); | ||||
|     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_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_intertechno_v3_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_came_test); | ||||
| @ -619,6 +778,7 @@ MU_TEST_SUITE(subghz) { | ||||
|     MU_RUN_TEST(subghz_encoder_magellan_test); | ||||
|     MU_RUN_TEST(subghz_encoder_intertechno_v3_test); | ||||
|     MU_RUN_TEST(subghz_encoder_clemsa_test); | ||||
|     MU_RUN_TEST(subghz_encoder_ansonic_test); | ||||
| 
 | ||||
|     MU_RUN_TEST(subghz_random_test); | ||||
|     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; | ||||
|                 } | ||||
|                 if(offset_new > 0) { | ||||
|                     offset_new = | ||||
|                         CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0); | ||||
|                     offset_new = CLAMP(offset_new, (int32_t)model->item_cnt, 0); | ||||
|                 } else { | ||||
|                     offset_new = 0; | ||||
|                 } | ||||
|  | ||||
| @ -3,9 +3,9 @@ | ||||
| #include "../archive_i.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 FILE_LIST_BUF_LEN 100 | ||||
| #define FILE_LIST_BUF_LEN 50 | ||||
| 
 | ||||
| static const char* tab_default_paths[] = { | ||||
|     [ArchiveTabFavorites] = "/app:favorites", | ||||
|  | ||||
| @ -657,12 +657,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { | ||||
|     bad_usb->st.state = BadUsbStateInit; | ||||
|     bad_usb->st.error[0] = '\0'; | ||||
| 
 | ||||
|     bad_usb->thread = furi_thread_alloc(); | ||||
|     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); | ||||
| 
 | ||||
|     bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); | ||||
|     furi_thread_start(bad_usb->thread); | ||||
|     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); | ||||
|     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; | ||||
|     } else { | ||||
|         result = true; | ||||
|  | ||||
| @ -29,6 +29,7 @@ struct GpioApp { | ||||
|     GpioTest* gpio_test; | ||||
|     GpioUsbUart* gpio_usb_uart; | ||||
|     UsbUartBridge* usb_uart_bridge; | ||||
|     UsbUartConfig* usb_uart_cfg; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|  | ||||
| @ -9,4 +9,5 @@ typedef enum { | ||||
|     GpioCustomEventErrorBack, | ||||
| 
 | ||||
|     GpioUsbUartEventConfig, | ||||
|     GpioUsbUartEventConfigSet, | ||||
| } GpioCustomEvent; | ||||
|  | ||||
| @ -9,8 +9,6 @@ typedef enum { | ||||
|     UsbUartLineIndexFlow, | ||||
| } LineIndex; | ||||
| 
 | ||||
| static UsbUartConfig* cfg_set; | ||||
| 
 | ||||
| static const char* vcp_ch[] = {"0 (CLI)", "1"}; | ||||
| static const char* uart_ch[] = {"13,14", "15,16"}; | ||||
| 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) { | ||||
|     UNUSED(context); | ||||
|     UNUSED(event); | ||||
|     GpioApp* app = context; | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| @ -38,55 +42,59 @@ void line_ensure_flow_invariant(GpioApp* app) { | ||||
|     // selected. This function enforces that invariant by resetting flow_pins
 | ||||
|     // 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; | ||||
|     variable_item_set_values_count(item, available_flow_pins); | ||||
| 
 | ||||
|     if(cfg_set->flow_pins >= available_flow_pins) { | ||||
|         cfg_set->flow_pins = 0; | ||||
|         usb_uart_set_config(app->usb_uart_bridge, cfg_set); | ||||
|     if(app->usb_uart_cfg->flow_pins >= available_flow_pins) { | ||||
|         app->usb_uart_cfg->flow_pins = 0; | ||||
| 
 | ||||
|         variable_item_set_current_value_index(item, cfg_set->flow_pins); | ||||
|         variable_item_set_current_value_text(item, flow_pins[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[app->usb_uart_cfg->flow_pins]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void line_vcp_cb(VariableItem* item) { | ||||
|     GpioApp* app = variable_item_get_context(item); | ||||
|     furi_assert(app); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, vcp_ch[index]); | ||||
| 
 | ||||
|     cfg_set->vcp_ch = index; | ||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); | ||||
|     app->usb_uart_cfg->vcp_ch = index; | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||
| } | ||||
| 
 | ||||
| static void line_port_cb(VariableItem* item) { | ||||
|     GpioApp* app = variable_item_get_context(item); | ||||
|     furi_assert(app); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, uart_ch[index]); | ||||
| 
 | ||||
|     if(index == 0) | ||||
|         cfg_set->uart_ch = FuriHalUartIdUSART1; | ||||
|         app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1; | ||||
|     else if(index == 1) | ||||
|         cfg_set->uart_ch = FuriHalUartIdLPUART1; | ||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); | ||||
|         app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1; | ||||
| 
 | ||||
|     line_ensure_flow_invariant(app); | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||
| } | ||||
| 
 | ||||
| static void line_flow_cb(VariableItem* item) { | ||||
|     GpioApp* app = variable_item_get_context(item); | ||||
|     furi_assert(app); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, flow_pins[index]); | ||||
| 
 | ||||
|     cfg_set->flow_pins = index; | ||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); | ||||
|     app->usb_uart_cfg->flow_pins = index; | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||
| } | ||||
| 
 | ||||
| static void line_baudrate_cb(VariableItem* item) { | ||||
|     GpioApp* app = variable_item_get_context(item); | ||||
|     furi_assert(app); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     char br_text[8]; | ||||
| @ -94,28 +102,29 @@ static void line_baudrate_cb(VariableItem* item) { | ||||
|     if(index > 0) { | ||||
|         snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); | ||||
|         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 { | ||||
|         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; | ||||
|     usb_uart_set_config(app->usb_uart_bridge, cfg_set); | ||||
|     app->usb_uart_cfg->baudrate_mode = index; | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_usb_uart_cfg_on_enter(void* context) { | ||||
|     GpioApp* app = context; | ||||
|     furi_assert(app); | ||||
|     VariableItemList* var_item_list = app->var_item_list; | ||||
| 
 | ||||
|     cfg_set = malloc(sizeof(UsbUartConfig)); | ||||
|     usb_uart_get_config(app->usb_uart_bridge, cfg_set); | ||||
|     app->usb_uart_cfg = malloc(sizeof(UsbUartConfig)); | ||||
|     usb_uart_get_config(app->usb_uart_bridge, app->usb_uart_cfg); | ||||
| 
 | ||||
|     VariableItem* item; | ||||
|     char br_text[8]; | ||||
| 
 | ||||
|     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_text(item, vcp_ch[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[app->usb_uart_cfg->vcp_ch]); | ||||
| 
 | ||||
|     item = variable_item_list_add( | ||||
|         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, | ||||
|         line_baudrate_cb, | ||||
|         app); | ||||
|     variable_item_set_current_value_index(item, cfg_set->baudrate_mode); | ||||
|     if(cfg_set->baudrate_mode > 0) { | ||||
|         snprintf(br_text, 7, "%lu", baudrate_list[cfg_set->baudrate_mode - 1]); | ||||
|     variable_item_set_current_value_index(item, app->usb_uart_cfg->baudrate_mode); | ||||
|     if(app->usb_uart_cfg->baudrate_mode > 0) { | ||||
|         snprintf(br_text, 7, "%lu", baudrate_list[app->usb_uart_cfg->baudrate_mode - 1]); | ||||
|         variable_item_set_current_value_text(item, br_text); | ||||
|     } 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); | ||||
|     variable_item_set_current_value_index(item, cfg_set->uart_ch); | ||||
|     variable_item_set_current_value_text(item, uart_ch[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[app->usb_uart_cfg->uart_ch]); | ||||
| 
 | ||||
|     item = variable_item_list_add( | ||||
|         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_text(item, flow_pins[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[app->usb_uart_cfg->flow_pins]); | ||||
|     app->var_item_flow = item; | ||||
|     line_ensure_flow_invariant(app); | ||||
| 
 | ||||
| @ -155,5 +165,5 @@ void gpio_scene_usb_uart_cfg_on_exit(void* context) { | ||||
|         GpioAppViewUsbUartCfg, | ||||
|         variable_item_list_get_selected_item_index(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 "usb_cdc.h" | ||||
| #include "cli/cli_vcp.h" | ||||
| #include <toolbox/api_lock.h> | ||||
| #include "cli/cli.h" | ||||
| 
 | ||||
| #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||
| @ -51,6 +52,8 @@ struct UsbUartBridge { | ||||
| 
 | ||||
|     UsbUartState st; | ||||
| 
 | ||||
|     FuriApiLock cfg_lock; | ||||
| 
 | ||||
|     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->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); | ||||
| 
 | ||||
|     usb_uart->tx_thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(usb_uart->tx_thread, "UsbUartTxWorker"); | ||||
|     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->tx_thread = | ||||
|         furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart); | ||||
| 
 | ||||
|     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_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; | ||||
|                 events |= WorkerEvtCtrlLineSet; | ||||
|             } | ||||
|             api_lock_unlock(usb_uart->cfg_lock); | ||||
|         } | ||||
|         if(events & WorkerEvtLineCfgSet) { | ||||
|             if(usb_uart->cfg.baudrate == 0) | ||||
| @ -338,11 +339,7 @@ UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { | ||||
| 
 | ||||
|     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); | ||||
| 
 | ||||
|     usb_uart->thread = furi_thread_alloc(); | ||||
|     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); | ||||
|     usb_uart->thread = furi_thread_alloc_ex("UsbUartWorker", 1024, usb_uart_worker, usb_uart); | ||||
| 
 | ||||
|     furi_thread_start(usb_uart->thread); | ||||
|     return usb_uart; | ||||
| @ -359,8 +356,10 @@ void usb_uart_disable(UsbUartBridge* usb_uart) { | ||||
| void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { | ||||
|     furi_assert(usb_uart); | ||||
|     furi_assert(cfg); | ||||
|     usb_uart->cfg_lock = api_lock_alloc_locked(); | ||||
|     memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); | ||||
|     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) { | ||||
|  | ||||
| @ -24,7 +24,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | ||||
|         &I_Power_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         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, | ||||
|         i, | ||||
| @ -36,7 +36,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | ||||
|         &I_Mute_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         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, | ||||
|         i, | ||||
| @ -48,7 +48,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | ||||
|         &I_Vol_up_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         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, | ||||
|         i, | ||||
| @ -60,7 +60,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | ||||
|         &I_Up_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         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, | ||||
|         i, | ||||
| @ -72,7 +72,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | ||||
|         &I_Vol_down_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         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, | ||||
|         i, | ||||
| @ -84,7 +84,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { | ||||
|         &I_Down_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         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, 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_result, MfUltralightReadAuthResult) | ||||
| 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_warn, MfUltralightUnlockWarn) | ||||
| ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess) | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexSave, | ||||
|     SubmenuIndexEmulate, | ||||
|     SubmenuIndexDetectReader, | ||||
|     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_add_item( | ||||
|         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, "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; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfClassicMenu, event.event); | ||||
|         if(event.event == SubmenuIndexSave) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexSave); | ||||
|             nfc->dev->format = NfcDeviceSaveFormatMifareClassic; | ||||
|             // Clear device name
 | ||||
|             nfc_device_set_name(nfc->dev, ""); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexEmulate) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexEmulate); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate); | ||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { | ||||
|                 DOLPHIN_DEED(DolphinDeedNfcAddEmulate); | ||||
| @ -53,9 +59,11 @@ bool nfc_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) | ||||
|                 DOLPHIN_DEED(DolphinDeedNfcEmulate); | ||||
|             } | ||||
|             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) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc->scene_manager, NfcSceneMfClassicMenu, SubmenuIndexInfo); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo); | ||||
|             consumed = true; | ||||
|         } | ||||
|  | ||||
| @ -19,10 +19,10 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) { | ||||
|     Submenu* submenu = nfc->submenu; | ||||
|     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, | ||||
|             "Unlock With Password", | ||||
|             "Unlock", | ||||
|             SubmenuIndexUnlock, | ||||
|             nfc_scene_mf_ultralight_menu_submenu_callback, | ||||
|             nfc); | ||||
|  | ||||
| @ -24,25 +24,29 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState | ||||
|     if(curr_state != state) { | ||||
|         if(state == NfcSceneMfUlReadStateDetecting) { | ||||
|             popup_reset(nfc->popup); | ||||
|             popup_set_text( | ||||
|                 nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); | ||||
|             popup_set_text(nfc->popup, "Apply the\ntarget card", 97, 24, AlignCenter, AlignTop); | ||||
|             popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|             nfc_blink_read_start(nfc); | ||||
|         } else if(state == NfcSceneMfUlReadStateReading) { | ||||
|             popup_reset(nfc->popup); | ||||
|             popup_set_header( | ||||
|                 nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); | ||||
|             popup_set_icon(nfc->popup, 12, 23, &A_Loading_24); | ||||
|             nfc_blink_detect_start(nfc); | ||||
|         } else if(state == NfcSceneMfUlReadStateNotSupportedCard) { | ||||
|             popup_reset(nfc->popup); | ||||
|             popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop); | ||||
|             popup_set_text( | ||||
|                 nfc->popup, | ||||
|                 "Only MIFARE\nUltralight & NTAG\n are supported", | ||||
|                 "Only MIFARE\nUltralight & NTAG\nare supported", | ||||
|                 4, | ||||
|                 22, | ||||
|                 AlignLeft, | ||||
|                 AlignTop); | ||||
|             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); | ||||
|     } | ||||
| @ -62,8 +66,6 @@ void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) { | ||||
|         &nfc->dev->dev_data, | ||||
|         nfc_scene_mf_ultralight_read_auth_worker_callback, | ||||
|         nfc); | ||||
| 
 | ||||
|     nfc_blink_read_start(nfc); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|             nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||
|         MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; | ||||
|         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; | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|     MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data); | ||||
|     Widget* widget = nfc->widget; | ||||
|     const char* title; | ||||
|     FuriString* temp_str; | ||||
|     temp_str = furi_string_alloc(); | ||||
| 
 | ||||
|     if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) { | ||||
|         widget_add_string_element( | ||||
|             widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "All pages are unlocked!"); | ||||
|         if(mf_ul_data->auth_success) { | ||||
|             title = "All pages are unlocked!"; | ||||
|         } else { | ||||
|         widget_add_string_element( | ||||
|             widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Not all pages unlocked!"); | ||||
|             title = "All unlocked but failed auth!"; | ||||
|         } | ||||
|     } else { | ||||
|         title = "Not all pages unlocked!"; | ||||
|     } | ||||
|     widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, title); | ||||
|     furi_string_set(temp_str, "UID:"); | ||||
|     for(size_t i = 0; i < nfc_data->uid_len; 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); | ||||
| 
 | ||||
|     furi_string_free(temp_str); | ||||
|     notification_message(nfc->notifications, &sequence_set_green_255); | ||||
|     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; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|             nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||
|         MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data; | ||||
|         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; | ||||
| @ -93,4 +111,6 @@ void nfc_scene_mf_ultralight_read_auth_result_on_exit(void* context) { | ||||
| 
 | ||||
|     // Clean views
 | ||||
|     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" | ||||
| 
 | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexMfUlUnlockMenuManual, | ||||
|     SubmenuIndexMfUlUnlockMenuAuto, | ||||
|     SubmenuIndexMfUlUnlockMenuAmeebo, | ||||
|     SubmenuIndexMfUlUnlockMenuXiaomi, | ||||
|     SubmenuIndexMfUlUnlockMenuManual, | ||||
| }; | ||||
| 
 | ||||
| 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 = | ||||
|         scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||
|     if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
|         "Enter Password Manually", | ||||
|         SubmenuIndexMfUlUnlockMenuManual, | ||||
|             "Unlock With Reader", | ||||
|             SubmenuIndexMfUlUnlockMenuAuto, | ||||
|             nfc_scene_mf_ultralight_unlock_menu_submenu_callback, | ||||
|             nfc); | ||||
|     } | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Auth As Ameebo", | ||||
| @ -32,10 +35,16 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { | ||||
|         nfc); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Auth As Xiaomi", | ||||
|         "Auth As Xiaomi Air Purifier", | ||||
|         SubmenuIndexMfUlUnlockMenuXiaomi, | ||||
|         nfc_scene_mf_ultralight_unlock_menu_submenu_callback, | ||||
|         nfc); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Enter Password Manually", | ||||
|         SubmenuIndexMfUlUnlockMenuManual, | ||||
|         nfc_scene_mf_ultralight_unlock_menu_submenu_callback, | ||||
|         nfc); | ||||
|     submenu_set_selected_item(submenu, state); | ||||
|     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; | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn); | ||||
|             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; | ||||
| } | ||||
|  | ||||
| @ -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) { | ||||
|     Nfc* nfc = context; | ||||
|     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_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback); | ||||
| 
 | ||||
|     if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { | ||||
|         // Build dialog text
 | ||||
|         MfUltralightAuth* auth = &nfc->dev->dev_data.mf_ul_auth; | ||||
|         FuriString* password_str = | ||||
|             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); | ||||
| } | ||||
| @ -28,6 +56,26 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve | ||||
| 
 | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     MfUltralightAuthMethod auth_method = nfc->dev->dev_data.mf_ul_data.auth_method; | ||||
|     if(auth_method == MfUltralightAuthMethodManual || auth_method == MfUltralightAuthMethodAuto) { | ||||
|         if(event.type == SceneManagerEventTypeCustom) { | ||||
|             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; | ||||
|         } | ||||
|     } else { | ||||
|         if(event.type == SceneManagerEventTypeCustom) { | ||||
|             if(event.event == DialogExResultCenter) { | ||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth); | ||||
| @ -35,6 +83,7 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve | ||||
|                 consumed = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| @ -43,5 +92,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_exit(void* context) { | ||||
|     Nfc* nfc = context; | ||||
| 
 | ||||
|     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); | ||||
|         if(data->data_size > data->data_read) { | ||||
|             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) { | ||||
|         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.event == GuiButtonTypeRight) { | ||||
|             if(protocol == NfcDeviceProtocolMifareDesfire) { | ||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireApp); | ||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfDesfireData); | ||||
|                 consumed = true; | ||||
|             } else if(protocol == NfcDeviceProtocolMifareUl) { | ||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); | ||||
|  | ||||
| @ -70,6 +70,8 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { | ||||
|             consumed = true; | ||||
|         } else if(event.event == NfcWorkerEventReadMfUltralight) { | ||||
|             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); | ||||
|             DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||
|             consumed = true; | ||||
|  | ||||
| @ -11,6 +11,8 @@ enum SubmenuIndex { | ||||
|     SubmenuIndexDelete, | ||||
|     SubmenuIndexInfo, | ||||
|     SubmenuIndexRestoreOriginal, | ||||
|     SubmenuIndexMfUlUnlockByReader, | ||||
|     SubmenuIndexMfUlUnlockByPassword, | ||||
| }; | ||||
| 
 | ||||
| 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, "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) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
| @ -106,6 +123,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexDetectReader) { | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); | ||||
|             DOLPHIN_DEED(DolphinDeedNfcDetectReader); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexWrite) { | ||||
|             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) { | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneRestoreOriginalConfirm); | ||||
|             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->thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(instance->thread, "SubGhzChat"); | ||||
|     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->thread = | ||||
|         furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); | ||||
|     instance->subghz_txrx = subghz_tx_rx_worker_alloc(); | ||||
|     instance->event_queue = furi_message_queue_alloc(80, sizeof(SubGhzChatEvent)); | ||||
|     return instance; | ||||
|  | ||||
| @ -25,7 +25,7 @@ TUPLE_DEF2( | ||||
|     (frequency, uint32_t), | ||||
|     (count, uint8_t), | ||||
|     (rssi_max, uint8_t)) | ||||
| /* Register globaly the oplist */ | ||||
| /* Register globally the oplist */ | ||||
| #define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ | ||||
|     TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) | ||||
| 
 | ||||
|  | ||||
| @ -5,8 +5,6 @@ | ||||
| 
 | ||||
| #define TAG "SubghzFrequencyAnalyzerWorker" | ||||
| 
 | ||||
| #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -95.0f | ||||
| 
 | ||||
| static const uint8_t subghz_preset_ook_58khz[][2] = { | ||||
|     {CC1101_MDMCFG4, 0b11110111}, // Rx BW filter is 58.035714kHz
 | ||||
|     /* 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}; | ||||
|     float rssi = 0; | ||||
|     uint32_t frequency = 0; | ||||
|     float rssi_temp = 0; | ||||
|     float rssi_temp = -127.0f; | ||||
|     uint32_t frequency_temp = 0; | ||||
|     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); | ||||
| 
 | ||||
|             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; | ||||
| 
 | ||||
|             if(instance->filVal) { | ||||
| @ -207,10 +205,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | ||||
|             // Deliver callback
 | ||||
|             if(instance->pair_callback) { | ||||
|                 instance->pair_callback( | ||||
|                     instance->context, | ||||
|                     frequency_rssi.frequency_fine, | ||||
|                     frequency_rssi.rssi_fine, | ||||
|                     true); | ||||
|                     instance->context, frequency_rssi.frequency_fine, rssi_temp, true); | ||||
|             } | ||||
|         } else if( // Deliver results coarse
 | ||||
|             (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); | ||||
| 
 | ||||
|             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; | ||||
|             if(instance->filVal) { | ||||
|                 frequency_rssi.frequency_coarse = | ||||
| @ -232,15 +227,12 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | ||||
|             // Deliver callback
 | ||||
|             if(instance->pair_callback) { | ||||
|                 instance->pair_callback( | ||||
|                     instance->context, | ||||
|                     frequency_rssi.frequency_coarse, | ||||
|                     frequency_rssi.rssi_coarse, | ||||
|                     true); | ||||
|                     instance->context, frequency_rssi.frequency_coarse, rssi_temp, true); | ||||
|             } | ||||
|         } else { | ||||
|             if(instance->sample_hold_counter > 0) { | ||||
|                 instance->sample_hold_counter--; | ||||
|                 if(instance->sample_hold_counter == 18) { | ||||
|                 if(instance->sample_hold_counter == 15) { | ||||
|                     if(instance->pair_callback) { | ||||
|                         instance->pair_callback( | ||||
|                             instance->context, frequency_temp, rssi_temp, false); | ||||
| @ -248,7 +240,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | ||||
|                 } | ||||
|             } else { | ||||
|                 instance->filVal = 0; | ||||
|                 if(instance->pair_callback) | ||||
|                 rssi_temp = -127.0f; | ||||
|                 instance->pair_callback(instance->context, 0, 0, false); | ||||
|             } | ||||
|         } | ||||
| @ -265,12 +257,8 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont | ||||
|     furi_assert(context); | ||||
|     SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); | ||||
| 
 | ||||
|     instance->thread = furi_thread_alloc(); | ||||
|     furi_thread_set_name(instance->thread, "SubGhzFAWorker"); | ||||
|     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); | ||||
| 
 | ||||
|     instance->thread = furi_thread_alloc_ex( | ||||
|         "SubGhzFAWorker", 2048, subghz_frequency_analyzer_worker_thread, instance); | ||||
|     SubGhz* subghz = context; | ||||
|     instance->setting = subghz->setting; | ||||
|     return instance; | ||||
|  | ||||
| @ -3,6 +3,8 @@ | ||||
| #include <furi_hal.h> | ||||
| #include "../subghz_i.h" | ||||
| 
 | ||||
| #define SUBGHZ_FREQUENCY_ANALYZER_THRESHOLD -93.0f | ||||
| 
 | ||||
| typedef struct SubGhzFrequencyAnalyzerWorker SubGhzFrequencyAnalyzerWorker; | ||||
| 
 | ||||
| typedef void (*SubGhzFrequencyAnalyzerWorkerPairCallback)( | ||||
|  | ||||
| @ -300,7 +300,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { | ||||
| 
 | ||||
|     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
 | ||||
|     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
 | ||||
|         subghz_receiver_free(receiver); | ||||
| @ -438,7 +438,7 @@ static void subghz_cli_command_print_usage() { | ||||
|         printf("\r\n"); | ||||
|         printf("  debug cmd:\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( | ||||
|             "\tencrypt_keeloq <path_decrypted_file> <path_encrypted_file> <IV:16 bytes in hex>\t - Encrypt keeloq manufacture keys\r\n"); | ||||
|         printf( | ||||
|  | ||||
| @ -13,8 +13,6 @@ | ||||
| #include <assets_icons.h> | ||||
| 
 | ||||
| #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) \ | ||||
|     snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000); | ||||
| @ -49,7 +47,7 @@ typedef struct { | ||||
| } SubGhzFrequencyAnalyzerModel; | ||||
| 
 | ||||
| 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( | ||||
| @ -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) { | ||||
|     uint8_t column_number = 0; | ||||
|     if(rssi) { | ||||
|         rssi = rssi / 3; | ||||
|         rssi = rssi / 3 + 2; | ||||
|         if(rssi > 20) rssi = 20; | ||||
|         for(uint8_t i = 1; i < rssi; i++) { | ||||
|             if(i > 20) break; | ||||
|             if(i % 4) { | ||||
|                 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); | ||||
|     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, offset_x + 64u, offset_y + 8u, AlignCenter, AlignBottom, "No records"); | ||||
|             canvas, offset_x + 64, offset_y + 8, AlignCenter, AlignBottom, "No records"); | ||||
|         return; | ||||
|     } else if(items_count > 3) { | ||||
|         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); | ||||
| 
 | ||||
|         // 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)); | ||||
|     } | ||||
| 
 | ||||
| @ -167,25 +178,20 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel | ||||
|     } else { | ||||
|         canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     // Frequency
 | ||||
|     canvas_set_font(canvas, FontBigNumbers); | ||||
|     snprintf( | ||||
|         buffer, | ||||
|         sizeof(buffer), | ||||
|         "%03ld.%03ld", | ||||
|         model->frequency / 1000000 % 1000, | ||||
|         model->frequency / 1000 % 1000); | ||||
|     SNPRINTF_FREQUENCY(buffer, model->frequency); | ||||
|     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_draw_str(canvas, 8, 30, buffer); | ||||
|     canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); | ||||
|     canvas_draw_str(canvas, 8, 29, buffer); | ||||
|     canvas_draw_icon(canvas, 96, 18, &I_MHz_25x11); | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|         } | ||||
|         (*item)->frequency = model->frequency; | ||||
|         (*item)->count = 1u; | ||||
|         (*item)->count = 1; | ||||
|         (*item)->rssi_max = model->rssi; | ||||
|         (*item)->seq = items_count; | ||||
|         return true; | ||||
| @ -394,9 +400,9 @@ void subghz_frequency_analyzer_enter(void* context) { | ||||
|             model->frequency = 0; | ||||
|             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; | ||||
|             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[2] = 0u; | ||||
|                 model->history_frequency[2] = 0; | ||||
|             SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency); | ||||
|         }, | ||||
|         true); | ||||
| @ -416,13 +422,13 @@ void subghz_frequency_analyzer_exit(void* context) { | ||||
|         instance->view, | ||||
|         SubGhzFrequencyAnalyzerModel * model, | ||||
|         { | ||||
|             model->rssi = 0u; | ||||
|             model->rssi = 0; | ||||
|             model->frequency = 0; | ||||
|             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; | ||||
|             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[2] = 0u; | ||||
|                 model->history_frequency[2] = 0; | ||||
|             SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency); | ||||
|         }, | ||||
|         true); | ||||
|  | ||||
| @ -58,13 +58,13 @@ struct U2fHid_packet { | ||||
| struct U2fHid { | ||||
|     FuriThread* thread; | ||||
|     FuriTimer* lock_timer; | ||||
|     struct U2fHid_packet packet; | ||||
|     uint8_t seq_id_last; | ||||
|     uint16_t req_buf_ptr; | ||||
|     uint32_t req_len_left; | ||||
|     uint32_t lock_cid; | ||||
|     bool lock; | ||||
|     U2fData* u2f_instance; | ||||
|     struct U2fHid_packet packet; | ||||
| }; | ||||
| 
 | ||||
| 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) { | ||||
|             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(len_cur < 7) { | ||||
|                         u2f_hid->req_len_left = 0; | ||||
|                         break; // Wrong chunk len
 | ||||
|                     } | ||||
|                     // Init packet
 | ||||
|                     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)) { | ||||
|                         u2f_hid->req_len_left = u2f_hid->packet.len - (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; | ||||
|                     if(len_cur > 0) memcpy(u2f_hid->packet.payload, &packet_buf[7], len_cur); | ||||
|                 } else { | ||||
|                     if(len_cur < 5) { | ||||
|                         u2f_hid->req_len_left = 0; | ||||
|                         break; // Wrong chunk len
 | ||||
|                     } | ||||
|                     // Continuation packet
 | ||||
|                     if(u2f_hid->req_len_left > 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); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             } while(0); | ||||
|         } | ||||
|         if(flags & WorkerEvtUnlock) { | ||||
|             u2f_hid->lock = false; | ||||
| @ -282,11 +297,7 @@ U2fHid* u2f_hid_start(U2fData* u2f_inst) { | ||||
| 
 | ||||
|     u2f_hid->u2f_instance = u2f_inst; | ||||
| 
 | ||||
|     u2f_hid->thread = furi_thread_alloc(); | ||||
|     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); | ||||
|     u2f_hid->thread = furi_thread_alloc_ex("U2fHidWorker", 2048, u2f_hid_worker, u2f_hid); | ||||
|     furi_thread_start(u2f_hid->thread); | ||||
|     return u2f_hid; | ||||
| } | ||||
|  | ||||
| @ -37,10 +37,10 @@ static void u2f_view_draw_callback(Canvas* canvas, void* _model) { | ||||
|     } else if(model->display_msg == U2fMsgSuccess) { | ||||
|         canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31); | ||||
|         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) { | ||||
|         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
 | ||||
|     furi_hal_usb_set_config(usb_config_prev, NULL); | ||||
|     dap_common_wait_for_deinit(); | ||||
|     dap_common_usb_free_name(); | ||||
|     dap_deinit_gpio(swd_pins_prev); | ||||
|     return 0; | ||||
| @ -441,19 +440,6 @@ static int32_t cdc_process(void* p) { | ||||
| /******************************* 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() { | ||||
|     DapApp* dap_app = malloc(sizeof(DapApp)); | ||||
|     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_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_control(dev, hid_control); | ||||
| 
 | ||||
|     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) { | ||||
|     dap_state.usb_dev = NULL; | ||||
| 
 | ||||
| @ -647,12 +636,6 @@ static void hid_deinit(usbd_device* dev) { | ||||
| 
 | ||||
|     usbd_reg_config(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) { | ||||
|  | ||||
| @ -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_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_hal_bt_hid.h> | ||||
| #include <furi_hal_usb_hid.h> | ||||
| #include <gui/elements.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; | ||||
|     Hid* hid; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -24,7 +25,7 @@ typedef struct { | ||||
|     bool back_pressed; | ||||
|     bool connected; | ||||
|     char key_string[5]; | ||||
| } BtHidKeyboardModel; | ||||
| } HidKeyboardModel; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t width; | ||||
| @ -32,13 +33,12 @@ typedef struct { | ||||
|     const Icon* icon; | ||||
|     char* shift_key; | ||||
|     uint8_t value; | ||||
| } BtHidKeyboardKey; | ||||
| } HidKeyboardKey; | ||||
| 
 | ||||
| typedef struct { | ||||
|     int8_t x; | ||||
|     int8_t y; | ||||
| } BtHidKeyboardPoint; | ||||
| 
 | ||||
| } HidKeyboardPoint; | ||||
| // 4 BY 12
 | ||||
| #define MARGIN_TOP 0 | ||||
| #define MARGIN_LEFT 4 | ||||
| @ -49,7 +49,7 @@ typedef struct { | ||||
| #define COLUMN_COUNT 12 | ||||
| 
 | ||||
| // 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 = "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 = 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 = 4, .icon = NULL, .key = " ", .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) { | ||||
|         *str = toupper((unsigned char)*str); | ||||
|         str++; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void bt_hid_keyboard_draw_key( | ||||
| static void hid_keyboard_draw_key( | ||||
|     Canvas* canvas, | ||||
|     BtHidKeyboardModel* model, | ||||
|     HidKeyboardModel* model, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     BtHidKeyboardKey key, | ||||
|     HidKeyboardKey key, | ||||
|     bool selected) { | ||||
|     if(!key.width) return; | ||||
| 
 | ||||
| @ -190,7 +190,7 @@ static void bt_hid_keyboard_draw_key( | ||||
|         if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || | ||||
|            (model->alt && key.value == HID_KEYBOARD_L_ALT) || | ||||
|            (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, | ||||
| @ -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); | ||||
|     BtHidKeyboardModel* model = context; | ||||
|     HidKeyboardModel* model = context; | ||||
| 
 | ||||
|     // Header
 | ||||
|     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)
 | ||||
|     uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; | ||||
|     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; | ||||
|         for(uint8_t i = 0; i < COLUMN_COUNT; i++) { | ||||
|             BtHidKeyboardKey key = keyboardKeyRow[i]; | ||||
|             HidKeyboardKey key = keyboardKeyRow[i]; | ||||
|             // Select when the button is hovered
 | ||||
|             // Select if the button is hovered within its width
 | ||||
|             // Select if back is clicked and its the backspace key
 | ||||
|             // Deselect when the button clicked or not hovered
 | ||||
|             bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; | ||||
|             bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; | ||||
|             bt_hid_keyboard_draw_key( | ||||
|             hid_keyboard_draw_key( | ||||
|                 canvas, | ||||
|                 model, | ||||
|                 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) { | ||||
|     BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x]; | ||||
| static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) { | ||||
|     HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x]; | ||||
|     // Use upper case if shift is toggled
 | ||||
|     bool useUppercase = model->shift; | ||||
|     // 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; | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
|     do { | ||||
|         if(((int8_t)model->y) + delta.y < 0) | ||||
|             model->y = ROW_COUNT - 1; | ||||
|         else | ||||
|             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 { | ||||
|         if(((int8_t)model->x) + delta.x < 0) | ||||
|             model->x = COLUMN_COUNT - 1; | ||||
|         else | ||||
|             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
 | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|         bt_hid_keyboard->view, | ||||
|         BtHidKeyboardModel * model, | ||||
|         hid_keyboard->view, | ||||
|         HidKeyboardModel * model, | ||||
|         { | ||||
|             if(event->key == InputKeyOk) { | ||||
|                 if(event->type == InputTypePress) { | ||||
|                     model->ok_pressed = true; | ||||
|                 } 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
 | ||||
|                     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 | ||||
|                             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) { | ||||
|                     // 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; | ||||
|                 } | ||||
|             } else if(event->key == InputKeyBack) { | ||||
| @ -325,66 +327,67 @@ static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* | ||||
|                 if(event->type == InputTypePress) { | ||||
|                     model->back_pressed = true; | ||||
|                 } else if(event->type == InputTypeShort) { | ||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); | ||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); | ||||
|                     hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE); | ||||
|                     hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE); | ||||
|                 } else if(event->type == InputTypeRelease) { | ||||
|                     model->back_pressed = false; | ||||
|                 } | ||||
|             } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { | ||||
|                 // Cycle the selected keys
 | ||||
|                 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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0}); | ||||
|                     hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0}); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         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); | ||||
|     BtHidKeyboard* bt_hid_keyboard = context; | ||||
|     HidKeyboard* hid_keyboard = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypeLong && event->key == InputKeyBack) { | ||||
|         furi_hal_bt_hid_kb_release_all(); | ||||
|         hid_hal_keyboard_release_all(hid_keyboard->hid); | ||||
|     } else { | ||||
|         bt_hid_keyboard_process(bt_hid_keyboard, event); | ||||
|         hid_keyboard_process(hid_keyboard, event); | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| BtHidKeyboard* bt_hid_keyboard_alloc() { | ||||
|     BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard)); | ||||
|     bt_hid_keyboard->view = view_alloc(); | ||||
|     view_set_context(bt_hid_keyboard->view, bt_hid_keyboard); | ||||
|     view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel)); | ||||
|     view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback); | ||||
|     view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback); | ||||
| HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { | ||||
|     HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard)); | ||||
|     hid_keyboard->view = view_alloc(); | ||||
|     hid_keyboard->hid = bt_hid; | ||||
|     view_set_context(hid_keyboard->view, hid_keyboard); | ||||
|     view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel)); | ||||
|     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) { | ||||
|     furi_assert(bt_hid_keyboard); | ||||
|     view_free(bt_hid_keyboard->view); | ||||
|     free(bt_hid_keyboard); | ||||
| void hid_keyboard_free(HidKeyboard* hid_keyboard) { | ||||
|     furi_assert(hid_keyboard); | ||||
|     view_free(hid_keyboard->view); | ||||
|     free(hid_keyboard); | ||||
| } | ||||
| 
 | ||||
| View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) { | ||||
|     furi_assert(bt_hid_keyboard); | ||||
|     return bt_hid_keyboard->view; | ||||
| View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) { | ||||
|     furi_assert(hid_keyboard); | ||||
|     return hid_keyboard->view; | ||||
| } | ||||
| 
 | ||||
| void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) { | ||||
|     furi_assert(bt_hid_keyboard); | ||||
| void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) { | ||||
|     furi_assert(hid_keyboard); | ||||
|     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 <furi.h> | ||||
| #include <furi_hal_bt_hid.h> | ||||
| #include <furi_hal_usb_hid.h> | ||||
| #include "hid_keynote.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; | ||||
|     Hid* hid; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -18,9 +19,9 @@ typedef struct { | ||||
|     bool ok_pressed; | ||||
|     bool back_pressed; | ||||
|     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); | ||||
|     if(dir == CanvasDirectionBottomToTop) { | ||||
|         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); | ||||
|     BtHidKeynoteModel* model = context; | ||||
|     HidKeynoteModel* model = context; | ||||
| 
 | ||||
|     // Header
 | ||||
|     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); | ||||
|         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); | ||||
| 
 | ||||
|     // 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); | ||||
|         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); | ||||
| 
 | ||||
|     // 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); | ||||
|         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); | ||||
| 
 | ||||
|     // 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); | ||||
|         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); | ||||
| 
 | ||||
|     // 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"); | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|         bt_hid_keynote->view, | ||||
|         BtHidKeynoteModel * model, | ||||
|         hid_keynote->view, | ||||
|         HidKeynoteModel * model, | ||||
|         { | ||||
|             if(event->type == InputTypePress) { | ||||
|                 if(event->key == InputKeyUp) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     model->back_pressed = true; | ||||
|                 } | ||||
|             } else if(event->type == InputTypeRelease) { | ||||
|                 if(event->key == InputKeyUp) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     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) { | ||||
|                     model->back_pressed = false; | ||||
|                 } | ||||
|             } else if(event->type == InputTypeShort) { | ||||
|                 if(event->key == InputKeyBack) { | ||||
|                     furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); | ||||
|                     furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); | ||||
|                     furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK); | ||||
|                     furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK); | ||||
|                     hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE); | ||||
|                     hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE); | ||||
|                     hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK); | ||||
|                     hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         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); | ||||
|     BtHidKeynote* bt_hid_keynote = context; | ||||
|     HidKeynote* hid_keynote = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypeLong && event->key == InputKeyBack) { | ||||
|         furi_hal_bt_hid_kb_release_all(); | ||||
|         hid_hal_keyboard_release_all(hid_keynote->hid); | ||||
|     } else { | ||||
|         bt_hid_keynote_process(bt_hid_keynote, event); | ||||
|         hid_keynote_process(hid_keynote, event); | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| BtHidKeynote* bt_hid_keynote_alloc() { | ||||
|     BtHidKeynote* bt_hid_keynote = malloc(sizeof(BtHidKeynote)); | ||||
|     bt_hid_keynote->view = view_alloc(); | ||||
|     view_set_context(bt_hid_keynote->view, bt_hid_keynote); | ||||
|     view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel)); | ||||
|     view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback); | ||||
|     view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback); | ||||
| HidKeynote* hid_keynote_alloc(Hid* hid) { | ||||
|     HidKeynote* hid_keynote = malloc(sizeof(HidKeynote)); | ||||
|     hid_keynote->view = view_alloc(); | ||||
|     hid_keynote->hid = hid; | ||||
|     view_set_context(hid_keynote->view, hid_keynote); | ||||
|     view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel)); | ||||
|     view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); | ||||
|     view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); | ||||
| 
 | ||||
|     return bt_hid_keynote; | ||||
|     return hid_keynote; | ||||
| } | ||||
| 
 | ||||
| void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) { | ||||
|     furi_assert(bt_hid_keynote); | ||||
|     view_free(bt_hid_keynote->view); | ||||
|     free(bt_hid_keynote); | ||||
| void hid_keynote_free(HidKeynote* hid_keynote) { | ||||
|     furi_assert(hid_keynote); | ||||
|     view_free(hid_keynote->view); | ||||
|     free(hid_keynote); | ||||
| } | ||||
| 
 | ||||
| View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) { | ||||
|     furi_assert(bt_hid_keynote); | ||||
|     return bt_hid_keynote->view; | ||||
| View* hid_keynote_get_view(HidKeynote* hid_keynote) { | ||||
|     furi_assert(hid_keynote); | ||||
|     return hid_keynote->view; | ||||
| } | ||||
| 
 | ||||
| void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) { | ||||
|     furi_assert(bt_hid_keynote); | ||||
| void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) { | ||||
|     furi_assert(hid_keynote); | ||||
|     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_hal_bt_hid.h> | ||||
| #include <furi_hal_usb_hid.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; | ||||
|     Hid* hid; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -17,9 +21,9 @@ typedef struct { | ||||
|     bool down_pressed; | ||||
|     bool ok_pressed; | ||||
|     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); | ||||
|     if(dir == CanvasDirectionBottomToTop) { | ||||
|         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); | ||||
|     BtHidMediaModel* model = context; | ||||
|     HidMediaModel* model = context; | ||||
| 
 | ||||
|     // Header
 | ||||
|     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_color(canvas, ColorWhite); | ||||
|     } | ||||
|     bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); | ||||
|     bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); | ||||
|     hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); | ||||
|     hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
|     // Right
 | ||||
| @ -87,8 +91,8 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); | ||||
|     bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); | ||||
|     hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); | ||||
|     hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
|     // 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_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, 102, 29, 102, 33); | ||||
|     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"); | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|         bt_hid_media->view, | ||||
|         BtHidMediaModel * model, | ||||
|         hid_media->view, | ||||
|         HidMediaModel * model, | ||||
|         { | ||||
|             if(event->key == InputKeyUp) { | ||||
|                 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) { | ||||
|                 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) { | ||||
|                 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) { | ||||
|                 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) { | ||||
|                 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); | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|         bt_hid_media->view, | ||||
|         BtHidMediaModel * model, | ||||
|         hid_media->view, | ||||
|         HidMediaModel * model, | ||||
|         { | ||||
|             if(event->key == InputKeyUp) { | ||||
|                 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) { | ||||
|                 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) { | ||||
|                 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) { | ||||
|                 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) { | ||||
|                 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); | ||||
| } | ||||
| 
 | ||||
| static bool bt_hid_media_input_callback(InputEvent* event, void* context) { | ||||
| static bool hid_media_input_callback(InputEvent* event, void* context) { | ||||
|     furi_assert(context); | ||||
|     BtHidMedia* bt_hid_media = context; | ||||
|     HidMedia* hid_media = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypePress) { | ||||
|         bt_hid_media_process_press(bt_hid_media, event); | ||||
|         hid_media_process_press(hid_media, event); | ||||
|         consumed = true; | ||||
|     } else if(event->type == InputTypeRelease) { | ||||
|         bt_hid_media_process_release(bt_hid_media, event); | ||||
|         hid_media_process_release(hid_media, event); | ||||
|         consumed = true; | ||||
|     } else if(event->type == InputTypeShort) { | ||||
|         if(event->key == InputKeyBack) { | ||||
|             furi_hal_bt_hid_consumer_key_release_all(); | ||||
|             hid_hal_consumer_key_release_all(hid_media->hid); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| BtHidMedia* bt_hid_media_alloc() { | ||||
|     BtHidMedia* bt_hid_media = malloc(sizeof(BtHidMedia)); | ||||
|     bt_hid_media->view = view_alloc(); | ||||
|     view_set_context(bt_hid_media->view, bt_hid_media); | ||||
|     view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel)); | ||||
|     view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback); | ||||
|     view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback); | ||||
| HidMedia* hid_media_alloc(Hid* hid) { | ||||
|     HidMedia* hid_media = malloc(sizeof(HidMedia)); | ||||
|     hid_media->view = view_alloc(); | ||||
|     hid_media->hid = hid; | ||||
|     view_set_context(hid_media->view, hid_media); | ||||
|     view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel)); | ||||
|     view_set_draw_callback(hid_media->view, hid_media_draw_callback); | ||||
|     view_set_input_callback(hid_media->view, hid_media_input_callback); | ||||
| 
 | ||||
|     return bt_hid_media; | ||||
|     return hid_media; | ||||
| } | ||||
| 
 | ||||
| void bt_hid_media_free(BtHidMedia* bt_hid_media) { | ||||
|     furi_assert(bt_hid_media); | ||||
|     view_free(bt_hid_media->view); | ||||
|     free(bt_hid_media); | ||||
| void hid_media_free(HidMedia* hid_media) { | ||||
|     furi_assert(hid_media); | ||||
|     view_free(hid_media->view); | ||||
|     free(hid_media); | ||||
| } | ||||
| 
 | ||||
| View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) { | ||||
|     furi_assert(bt_hid_media); | ||||
|     return bt_hid_media->view; | ||||
| View* hid_media_get_view(HidMedia* hid_media) { | ||||
|     furi_assert(hid_media); | ||||
|     return hid_media->view; | ||||
| } | ||||
| 
 | ||||
| void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) { | ||||
|     furi_assert(bt_hid_media); | ||||
| void hid_media_set_connected_status(HidMedia* hid_media, bool connected) { | ||||
|     furi_assert(hid_media); | ||||
|     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