Преглед изворни кода

Merge remote-tracking branch 'origin/dev' into feature_wifi_marauder_app

0xchocolate пре 3 година
родитељ
комит
df8fae1eb9
100 измењених фајлова са 1923 додато и 724 уклоњено
  1. 3 3
      CONTRIBUTING.md
  2. 1 1
      applications/debug/accessor/helpers/wiegand.cpp
  3. 1 1
      applications/debug/bt_debug_app/bt_debug_app.c
  4. 1 1
      applications/debug/display_test/display_test.c
  5. 10 0
      applications/debug/rpc_debug_app/application.fam
  6. 138 0
      applications/debug/rpc_debug_app/rpc_debug_app.c
  7. 54 0
      applications/debug/rpc_debug_app/rpc_debug_app.h
  8. 30 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c
  9. 29 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h
  10. 8 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h
  11. 40 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c
  12. 60 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c
  13. 40 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c
  14. 70 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c
  15. 57 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c
  16. 30 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c
  17. 57 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c
  18. 58 0
      applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c
  19. 1 5
      applications/debug/uart_echo/uart_echo.c
  20. 2 2
      applications/debug/unit_tests/furi/furi_memmgr_test.c
  21. 1 1
      applications/debug/unit_tests/nfc/nfc_test.c
  22. 4 10
      applications/debug/unit_tests/storage/storage_test.c
  23. 3 3
      applications/debug/unit_tests/stream/stream_test.c
  24. 161 1
      applications/debug/unit_tests/subghz/subghz_test.c
  25. 1 2
      applications/main/archive/helpers/archive_browser.c
  26. 2 2
      applications/main/archive/helpers/archive_browser.h
  27. 1 6
      applications/main/bad_usb/bad_usb_script.c
  28. 1 1
      applications/main/fap_loader/elf_cpp/elf_hashtable.cpp
  29. 1 0
      applications/main/gpio/gpio_app_i.h
  30. 1 0
      applications/main/gpio/gpio_custom_event.h
  31. 44 34
      applications/main/gpio/scenes/gpio_scene_usb_uart_config.c
  32. 9 10
      applications/main/gpio/usb_uart_bridge.c
  33. 6 6
      applications/main/infrared/scenes/infrared_scene_universal_tv.c
  34. 1 0
      applications/main/nfc/scenes/nfc_scene_config.h
  35. 14 6
      applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c
  36. 2 2
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c
  37. 18 7
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c
  38. 26 6
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c
  39. 64 0
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c
  40. 21 8
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c
  41. 61 10
      applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c
  42. 15 1
      applications/main/nfc/scenes/nfc_scene_nfc_data_info.c
  43. 2 0
      applications/main/nfc/scenes/nfc_scene_read.c
  44. 24 0
      applications/main/nfc/scenes/nfc_scene_saved_menu.c
  45. 2 5
      applications/main/subghz/helpers/subghz_chat.c
  46. 1 1
      applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h
  47. 10 22
      applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c
  48. 2 0
      applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h
  49. 3 3
      applications/main/subghz/subghz_cli.c
  50. 38 25
      applications/main/subghz/views/subghz_frequency_analyzer.c
  51. 19 8
      applications/main/u2f/u2f_hid.c
  52. 2 2
      applications/main/u2f/views/u2f_view.c
  53. 0 10
      applications/plugins/bt_hid_app/application.fam
  54. 0 216
      applications/plugins/bt_hid_app/bt_hid.c
  55. 0 41
      applications/plugins/bt_hid_app/bt_hid.h
  56. 0 13
      applications/plugins/bt_hid_app/views/bt_hid_keyboard.h
  57. 0 13
      applications/plugins/bt_hid_app/views/bt_hid_keynote.h
  58. 0 13
      applications/plugins/bt_hid_app/views/bt_hid_media.h
  59. 0 13
      applications/plugins/bt_hid_app/views/bt_hid_mouse.h
  60. 0 13
      applications/plugins/bt_hid_app/views/bt_hid_tiktok.h
  61. 0 14
      applications/plugins/dap_link/dap_link.c
  62. 0 17
      applications/plugins/dap_link/usb/dap_v2_usb.c
  63. 0 2
      applications/plugins/dap_link/usb/dap_v2_usb.h
  64. 24 0
      applications/plugins/hid_app/application.fam
  65. 0 0
      applications/plugins/hid_app/assets/Arr_dwn_7x9.png
  66. 0 0
      applications/plugins/hid_app/assets/Arr_up_7x9.png
  67. 0 0
      applications/plugins/hid_app/assets/Ble_connected_15x15.png
  68. 0 0
      applications/plugins/hid_app/assets/Ble_disconnected_15x15.png
  69. 0 0
      applications/plugins/hid_app/assets/ButtonDown_7x4.png
  70. 0 0
      applications/plugins/hid_app/assets/ButtonLeft_4x7.png
  71. 0 0
      applications/plugins/hid_app/assets/ButtonRight_4x7.png
  72. 0 0
      applications/plugins/hid_app/assets/ButtonUp_7x4.png
  73. 0 0
      applications/plugins/hid_app/assets/Button_18x18.png
  74. 0 0
      applications/plugins/hid_app/assets/Circles_47x47.png
  75. 0 0
      applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png
  76. 0 0
      applications/plugins/hid_app/assets/Like_def_11x9.png
  77. 0 0
      applications/plugins/hid_app/assets/Like_pressed_17x17.png
  78. 0 0
      applications/plugins/hid_app/assets/Ok_btn_9x9.png
  79. 0 0
      applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png
  80. 0 0
      applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png
  81. 0 0
      applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png
  82. 0 0
      applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png
  83. 0 0
      applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png
  84. 0 0
      applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png
  85. 0 0
      applications/plugins/hid_app/assets/Pressed_Button_13x13.png
  86. 0 0
      applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png
  87. 0 0
      applications/plugins/hid_app/assets/Space_65x18.png
  88. 0 0
      applications/plugins/hid_app/assets/Voldwn_6x6.png
  89. 0 0
      applications/plugins/hid_app/assets/Volup_8x6.png
  90. 365 0
      applications/plugins/hid_app/hid.c
  91. 60 0
      applications/plugins/hid_app/hid.h
  92. 0 0
      applications/plugins/hid_app/hid_ble_10px.png
  93. BIN
      applications/plugins/hid_app/hid_usb_10px.png
  94. 9 0
      applications/plugins/hid_app/views.h
  95. 63 60
      applications/plugins/hid_app/views/hid_keyboard.c
  96. 14 0
      applications/plugins/hid_app/views/hid_keyboard.h
  97. 55 53
      applications/plugins/hid_app/views/hid_keynote.c
  98. 14 0
      applications/plugins/hid_app/views/hid_keynote.h
  99. 56 51
      applications/plugins/hid_app/views/hid_media.c
  100. 13 0
      applications/plugins/hid_app/views/hid_media.h

+ 3 - 3
CONTRIBUTING.md

@@ -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.
 

+ 1 - 1
applications/debug/accessor/helpers/wiegand.cpp

@@ -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

+ 1 - 1
applications/debug/bt_debug_app/bt_debug_app.c

@@ -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;

+ 1 - 1
applications/debug/display_test/display_test.c

@@ -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 - 0
applications/debug/rpc_debug_app/application.fam

@@ -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 - 0
applications/debug/rpc_debug_app/rpc_debug_app.c

@@ -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 - 0
applications/debug/rpc_debug_app/rpc_debug_app.h

@@ -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;

+ 30 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c

@@ -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,
+};

+ 29 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h

@@ -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

+ 8 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h

@@ -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)

+ 40 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c

@@ -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);
+}

+ 60 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c

@@ -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);
+}

+ 40 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c

@@ -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);
+}

+ 70 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c

@@ -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);
+}

+ 57 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c

@@ -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);
+}

+ 30 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c

@@ -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);
+}

+ 57 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c

@@ -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);
+}

+ 58 - 0
applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c

@@ -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);
+}

+ 1 - 5
applications/debug/uart_echo/uart_echo.c

@@ -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;

+ 2 - 2
applications/debug/unit_tests/furi/furi_memmgr_test.c

@@ -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

+ 1 - 1
applications/debug/unit_tests/nfc/nfc_test.c

@@ -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;
             }

+ 4 - 10
applications/debug/unit_tests/storage/storage_test.c

@@ -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

+ 3 - 3
applications/debug/unit_tests/stream/stream_test.c

@@ -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));

+ 161 - 1
applications/debug/unit_tests/subghz/subghz_test.c

@@ -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();

+ 1 - 2
applications/main/archive/helpers/archive_browser.c

@@ -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;
                 }

+ 2 - 2
applications/main/archive/helpers/archive_browser.h

@@ -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",

+ 1 - 6
applications/main/bad_usb/bad_usb_script.c

@@ -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;
 }

+ 1 - 1
applications/main/fap_loader/elf_cpp/elf_hashtable.cpp

@@ -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;

+ 1 - 0
applications/main/gpio/gpio_app_i.h

@@ -29,6 +29,7 @@ struct GpioApp {
     GpioTest* gpio_test;
     GpioUsbUart* gpio_usb_uart;
     UsbUartBridge* usb_uart_bridge;
+    UsbUartConfig* usb_uart_cfg;
 };
 
 typedef enum {

+ 1 - 0
applications/main/gpio/gpio_custom_event.h

@@ -9,4 +9,5 @@ typedef enum {
     GpioCustomEventErrorBack,
 
     GpioUsbUartEventConfig,
+    GpioUsbUartEventConfigSet,
 } GpioCustomEvent;

+ 44 - 34
applications/main/gpio/scenes/gpio_scene_usb_uart_config.c

@@ -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);
 }

+ 9 - 10
applications/main/gpio/usb_uart_bridge.c

@@ -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) {

+ 6 - 6
applications/main/infrared/scenes/infrared_scene_universal_tv.c

@@ -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");

+ 1 - 0
applications/main/nfc/scenes/nfc_scene_config.h

@@ -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)

+ 14 - 6
applications/main/nfc/scenes/nfc_scene_mf_classic_menu.c

@@ -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;
         }

+ 2 - 2
applications/main/nfc/scenes/nfc_scene_mf_ultralight_menu.c

@@ -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);

+ 18 - 7
applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c

@@ -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;
 }

+ 26 - 6
applications/main/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c

@@ -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 {
+            title = "All unlocked but failed auth!";
+        }
     } else {
-        widget_add_string_element(
-            widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Not all pages unlocked!");
+        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);
 }

+ 64 - 0
applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_auto.c

@@ -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);
+}

+ 21 - 8
applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c

@@ -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,22 +19,30 @@ 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,
+            "Unlock With Reader",
+            SubmenuIndexMfUlUnlockMenuAuto,
+            nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
+            nfc);
+    }
     submenu_add_item(
         submenu,
-        "Enter Password Manually",
-        SubmenuIndexMfUlUnlockMenuManual,
+        "Auth As Ameebo",
+        SubmenuIndexMfUlUnlockMenuAmeebo,
         nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
         nfc);
     submenu_add_item(
         submenu,
-        "Auth As Ameebo",
-        SubmenuIndexMfUlUnlockMenuAmeebo,
+        "Auth As Xiaomi Air Purifier",
+        SubmenuIndexMfUlUnlockMenuXiaomi,
         nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
         nfc);
     submenu_add_item(
         submenu,
-        "Auth As Xiaomi",
-        SubmenuIndexMfUlUnlockMenuXiaomi,
+        "Enter Password Manually",
+        SubmenuIndexMfUlUnlockMenuManual,
         nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
         nfc);
     submenu_set_selected_item(submenu, state);
@@ -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;
 }

+ 61 - 10
applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c

@@ -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);
 
-    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");
+    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,12 +56,33 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
 
     bool consumed = false;
 
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == DialogExResultCenter) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth);
-            DOLPHIN_DEED(DolphinDeedNfcRead);
+    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);
+                DOLPHIN_DEED(DolphinDeedNfcRead);
+                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);
 }

+ 15 - 1
applications/main/nfc/scenes/nfc_scene_nfc_data_info.c

@@ -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);

+ 2 - 0
applications/main/nfc/scenes/nfc_scene_read.c

@@ -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;

+ 24 - 0
applications/main/nfc/scenes/nfc_scene_saved_menu.c

@@ -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;
         }
     }
 

+ 2 - 5
applications/main/subghz/helpers/subghz_chat.c

@@ -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;

+ 1 - 1
applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h

@@ -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)
 

+ 10 - 22
applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c

@@ -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,8 +240,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) {
                 }
             } else {
                 instance->filVal = 0;
-                if(instance->pair_callback)
-                    instance->pair_callback(instance->context, 0, 0, false);
+                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;

+ 2 - 0
applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h

@@ -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)(

+ 3 - 3
applications/main/subghz/subghz_cli.c

@@ -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(

+ 38 - 25
applications/main/subghz/views/subghz_frequency_analyzer.c

@@ -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,27 @@ 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) {
+    furi_assert(model);
+    M_LET((cmp, model->log_frequency_order_by), SubGhzFrequencyAnalyzerLogItemArray_compare_by_t)
+    SubGhzFrequencyAnalyzerLogItemArray_sort_fo(
+        model->log_frequency, SubGhzFrequencyAnalyzerLogItemArray_compare_by_as_interface(cmp));
 }
 
 static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) {
@@ -292,7 +305,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 +407,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 +429,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);

+ 19 - 8
applications/main/u2f/u2f_hid.c

@@ -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;
 }

+ 2 - 2
applications/main/u2f/views/u2f_view.c

@@ -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");
     }
 }
 

+ 0 - 10
applications/plugins/bt_hid_app/application.fam

@@ -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",
-)

+ 0 - 216
applications/plugins/bt_hid_app/bt_hid.c

@@ -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;
-}

+ 0 - 41
applications/plugins/bt_hid_app/bt_hid.h

@@ -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;

+ 0 - 13
applications/plugins/bt_hid_app/views/bt_hid_keyboard.h

@@ -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);

+ 0 - 13
applications/plugins/bt_hid_app/views/bt_hid_keynote.h

@@ -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);

+ 0 - 13
applications/plugins/bt_hid_app/views/bt_hid_media.h

@@ -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);

+ 0 - 13
applications/plugins/bt_hid_app/views/bt_hid_mouse.h

@@ -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);

+ 0 - 13
applications/plugins/bt_hid_app/views/bt_hid_tiktok.h

@@ -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);

+ 0 - 14
applications/plugins/dap_link/dap_link.c

@@ -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);

+ 0 - 17
applications/plugins/dap_link/usb/dap_v2_usb.c

@@ -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) {

+ 0 - 2
applications/plugins/dap_link/usb/dap_v2_usb.h

@@ -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 - 0
applications/plugins/hid_app/application.fam

@@ -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",
+)

+ 0 - 0
applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png → applications/plugins/hid_app/assets/Arr_dwn_7x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Arr_up_7x9.png → applications/plugins/hid_app/assets/Arr_up_7x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Ble_connected_15x15.png → applications/plugins/hid_app/assets/Ble_connected_15x15.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Ble_disconnected_15x15.png → applications/plugins/hid_app/assets/Ble_disconnected_15x15.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/ButtonDown_7x4.png → applications/plugins/hid_app/assets/ButtonDown_7x4.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/ButtonLeft_4x7.png → applications/plugins/hid_app/assets/ButtonLeft_4x7.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/ButtonRight_4x7.png → applications/plugins/hid_app/assets/ButtonRight_4x7.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/ButtonUp_7x4.png → applications/plugins/hid_app/assets/ButtonUp_7x4.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Button_18x18.png → applications/plugins/hid_app/assets/Button_18x18.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Circles_47x47.png → applications/plugins/hid_app/assets/Circles_47x47.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png → applications/plugins/hid_app/assets/Left_mouse_icon_9x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Like_def_11x9.png → applications/plugins/hid_app/assets/Like_def_11x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Like_pressed_17x17.png → applications/plugins/hid_app/assets/Like_pressed_17x17.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Ok_btn_9x9.png → applications/plugins/hid_app/assets/Ok_btn_9x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Ok_btn_pressed_13x13.png → applications/plugins/hid_app/assets/Ok_btn_pressed_13x13.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Pin_arrow_down_7x9.png → applications/plugins/hid_app/assets/Pin_arrow_down_7x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Pin_arrow_left_9x7.png → applications/plugins/hid_app/assets/Pin_arrow_left_9x7.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Pin_arrow_right_9x7.png → applications/plugins/hid_app/assets/Pin_arrow_right_9x7.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Pin_arrow_up_7x9.png → applications/plugins/hid_app/assets/Pin_arrow_up_7x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Pin_back_arrow_10x8.png → applications/plugins/hid_app/assets/Pin_back_arrow_10x8.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png → applications/plugins/hid_app/assets/Pressed_Button_13x13.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Right_mouse_icon_9x9.png → applications/plugins/hid_app/assets/Right_mouse_icon_9x9.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Space_65x18.png → applications/plugins/hid_app/assets/Space_65x18.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Voldwn_6x6.png → applications/plugins/hid_app/assets/Voldwn_6x6.png


+ 0 - 0
applications/plugins/bt_hid_app/assets/Volup_8x6.png → applications/plugins/hid_app/assets/Volup_8x6.png


+ 365 - 0
applications/plugins/hid_app/hid.c

@@ -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 - 0
applications/plugins/hid_app/hid.h

@@ -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);

+ 0 - 0
applications/plugins/bt_hid_app/bt_remote_10px.png → applications/plugins/hid_app/hid_ble_10px.png


BIN
applications/plugins/hid_app/hid_usb_10px.png


+ 9 - 0
applications/plugins/hid_app/views.h

@@ -0,0 +1,9 @@
+typedef enum {
+    HidViewSubmenu,
+    HidViewKeynote,
+    HidViewKeyboard,
+    HidViewMedia,
+    HidViewMouse,
+    BtHidViewTikTok,
+    HidViewExitConfirm,
+} HidView;

+ 63 - 60
applications/plugins/bt_hid_app/views/bt_hid_keyboard.c → applications/plugins/hid_app/views/hid_keyboard.c

@@ -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 - 0
applications/plugins/hid_app/views/hid_keyboard.h

@@ -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);

+ 55 - 53
applications/plugins/bt_hid_app/views/bt_hid_keynote.c → applications/plugins/hid_app/views/hid_keynote.c

@@ -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 - 0
applications/plugins/hid_app/views/hid_keynote.h

@@ -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);

+ 56 - 51
applications/plugins/bt_hid_app/views/bt_hid_media.c → applications/plugins/hid_app/views/hid_media.c

@@ -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 - 0
applications/plugins/hid_app/views/hid_media.h

@@ -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);

Неке датотеке нису приказане због велике количине промена