Просмотр исходного кода

[FL-2568] Infrared C port (#1326)

* Add skeleton for infrared C port, rename old app
* Add scene stubs
* Add more views
* Misc changes
* Add remote and signal class stubs
* Complete infrared signal class
* Add remote button class stub
* Check if button contains a signal during destruction
* Complete infrared signal class more
* Implement remote storing
* Implement remote loading
* Fix error handling
* Implement remote transmitting
* Rename scene
* Canonise event consumption
* Implement remote learning (stub)
* Implement learn success screen (stub)
* Implement AskBack scene
* Improve remote saving&loading
* Fix remote file name
* Add LearnDone scene
* Switch from Remote scene correctly
* Add SceneButtonSelect
* Remove unneeded assert
* Add new SceneManager method
* Use new SceneManager method in Infrared
* Implement renaming of buttons and remotes
* Implement deleting of buttons and remotes
* Add universal remotes list
* Add brute force code
* Brute force code improvements
* Partially implement Universal Remote GUI
* Fix wrong singnal handling
* Fully implement Universal Remote
* Use standard custom events everywhere
* Return infrared CLI
* Remove old Infrared app
* Change container name
* Fix scene order
* Put ButtonPanel into stack only when needed
* Show loading animation during slow operations
* Do not hardcode Loading widget coordinates
* Switch Loading widget orientation as needed
* Save Start scene state
* Save Remote scene state
* Save Edit scene state
* Save EditButtonSelect scene state
* Do not use scene state
* Use string_t instead of const char* for brevity
* Fix memory leak
* Fix saving raw remotes
* Add Infrared debug menu
* Add debug view
* Move Infrared monitor into Infrared application
* Remove old Infrared monitor app
* Use common signal received callback everywhere
Georgii Surkov 3 лет назад
Родитель
Сommit
a8acfcabb4
77 измененных файлов с 2940 добавлено и 3859 удалено
  1. 0 9
      applications/applications.c
  2. 0 9
      applications/applications.mk
  3. 2 2
      applications/gui/modules/loading.c
  4. 19 0
      applications/gui/scene_manager.c
  5. 14 0
      applications/gui/scene_manager.h
  6. 0 157
      applications/infrared/helpers/infrared_parser.cpp
  7. 0 48
      applications/infrared/helpers/infrared_parser.h
  8. 397 0
      applications/infrared/infrared.c
  9. 3 0
      applications/infrared/infrared.h
  10. 0 250
      applications/infrared/infrared_app.cpp
  11. 0 326
      applications/infrared/infrared_app.h
  12. 0 93
      applications/infrared/infrared_app_brute_force.cpp
  13. 0 67
      applications/infrared/infrared_app_brute_force.h
  14. 0 48
      applications/infrared/infrared_app_event.h
  15. 0 266
      applications/infrared/infrared_app_remote_manager.cpp
  16. 0 189
      applications/infrared/infrared_app_remote_manager.h
  17. 0 116
      applications/infrared/infrared_app_signal.cpp
  18. 0 134
      applications/infrared/infrared_app_signal.h
  19. 0 163
      applications/infrared/infrared_app_view_manager.cpp
  20. 0 164
      applications/infrared/infrared_app_view_manager.h
  21. 153 0
      applications/infrared/infrared_brute_force.c
  22. 22 0
      applications/infrared/infrared_brute_force.h
  23. 52 53
      applications/infrared/infrared_cli.c
  24. 51 0
      applications/infrared/infrared_custom_event.h
  25. 132 0
      applications/infrared/infrared_i.h
  26. 176 0
      applications/infrared/infrared_remote.c
  27. 28 0
      applications/infrared/infrared_remote.h
  28. 38 0
      applications/infrared/infrared_remote_button.c
  29. 14 0
      applications/infrared/infrared_remote_button.h
  30. 0 9
      applications/infrared/infrared_runner.cpp
  31. 264 0
      applications/infrared/infrared_signal.c
  32. 41 0
      applications/infrared/infrared_signal.h
  33. 0 305
      applications/infrared/scene/infrared_app_scene.h
  34. 0 73
      applications/infrared/scene/infrared_app_scene_ask_back.cpp
  35. 0 79
      applications/infrared/scene/infrared_app_scene_edit.cpp
  36. 0 100
      applications/infrared/scene/infrared_app_scene_edit_delete.cpp
  37. 0 38
      applications/infrared/scene/infrared_app_scene_edit_delete_done.cpp
  38. 0 58
      applications/infrared/scene/infrared_app_scene_edit_key_select.cpp
  39. 0 83
      applications/infrared/scene/infrared_app_scene_edit_rename.cpp
  40. 0 30
      applications/infrared/scene/infrared_app_scene_edit_rename_done.cpp
  41. 0 76
      applications/infrared/scene/infrared_app_scene_learn.cpp
  42. 0 41
      applications/infrared/scene/infrared_app_scene_learn_done.cpp
  43. 0 60
      applications/infrared/scene/infrared_app_scene_learn_enter_name.cpp
  44. 0 142
      applications/infrared/scene/infrared_app_scene_learn_success.cpp
  45. 0 131
      applications/infrared/scene/infrared_app_scene_remote.cpp
  46. 0 45
      applications/infrared/scene/infrared_app_scene_remote_list.cpp
  47. 0 68
      applications/infrared/scene/infrared_app_scene_start.cpp
  48. 0 57
      applications/infrared/scene/infrared_app_scene_universal.cpp
  49. 0 107
      applications/infrared/scene/infrared_app_scene_universal_common.cpp
  50. 0 123
      applications/infrared/scene/infrared_app_scene_universal_tv.cpp
  51. 92 0
      applications/infrared/scenes/common/infrared_scene_universal_common.c
  52. 8 0
      applications/infrared/scenes/common/infrared_scene_universal_common.h
  53. 30 0
      applications/infrared/scenes/infrared_scene.c
  54. 29 0
      applications/infrared/scenes/infrared_scene.h
  55. 59 0
      applications/infrared/scenes/infrared_scene_ask_back.c
  56. 17 0
      applications/infrared/scenes/infrared_scene_config.h
  57. 68 0
      applications/infrared/scenes/infrared_scene_debug.c
  58. 101 0
      applications/infrared/scenes/infrared_scene_edit.c
  59. 63 0
      applications/infrared/scenes/infrared_scene_edit_button_select.c
  60. 112 0
      applications/infrared/scenes/infrared_scene_edit_delete.c
  61. 46 0
      applications/infrared/scenes/infrared_scene_edit_delete_done.c
  62. 107 0
      applications/infrared/scenes/infrared_scene_edit_rename.c
  63. 35 0
      applications/infrared/scenes/infrared_scene_edit_rename_done.c
  64. 46 0
      applications/infrared/scenes/infrared_scene_learn.c
  65. 44 0
      applications/infrared/scenes/infrared_scene_learn_done.c
  66. 66 0
      applications/infrared/scenes/infrared_scene_learn_enter_name.c
  67. 131 0
      applications/infrared/scenes/infrared_scene_learn_success.c
  68. 119 0
      applications/infrared/scenes/infrared_scene_remote.c
  69. 44 0
      applications/infrared/scenes/infrared_scene_remote_list.c
  70. 83 0
      applications/infrared/scenes/infrared_scene_start.c
  71. 53 0
      applications/infrared/scenes/infrared_scene_universal.c
  72. 111 0
      applications/infrared/scenes/infrared_scene_universal_tv.c
  73. 59 0
      applications/infrared/views/infrared_debug_view.c
  74. 11 0
      applications/infrared/views/infrared_debug_view.h
  75. 0 0
      applications/infrared/views/infrared_progress_view.c
  76. 0 0
      applications/infrared/views/infrared_progress_view.h
  77. 0 140
      applications/infrared_monitor/infrared_monitor.c

+ 0 - 9
applications/applications.c

@@ -29,7 +29,6 @@ extern int32_t display_test_app(void* p);
 extern int32_t gpio_app(void* p);
 extern int32_t gpio_app(void* p);
 extern int32_t ibutton_app(void* p);
 extern int32_t ibutton_app(void* p);
 extern int32_t infrared_app(void* p);
 extern int32_t infrared_app(void* p);
-extern int32_t infrared_monitor_app(void* p);
 extern int32_t keypad_test_app(void* p);
 extern int32_t keypad_test_app(void* p);
 extern int32_t lfrfid_app(void* p);
 extern int32_t lfrfid_app(void* p);
 extern int32_t lfrfid_debug_app(void* p);
 extern int32_t lfrfid_debug_app(void* p);
@@ -412,14 +411,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = {
      .flags = FlipperApplicationFlagDefault},
      .flags = FlipperApplicationFlagDefault},
 #endif
 #endif
 
 
-#ifdef APP_INFRARED_MONITOR
-    {.app = infrared_monitor_app,
-     .name = "Infrared Monitor",
-     .stack_size = 1024,
-     .icon = NULL,
-     .flags = FlipperApplicationFlagDefault},
-#endif
-
 #ifdef APP_LF_RFID
 #ifdef APP_LF_RFID
     {.app = lfrfid_debug_app,
     {.app = lfrfid_debug_app,
      .name = "LF-RFID Debug",
      .name = "LF-RFID Debug",

+ 0 - 9
applications/applications.mk

@@ -51,7 +51,6 @@ APP_SNAKE_GAME = 1
 # Debug
 # Debug
 APP_ACCESSOR = 1
 APP_ACCESSOR = 1
 APP_BLINK = 1
 APP_BLINK = 1
-APP_INFRARED_MONITOR = 1
 APP_KEYPAD_TEST = 1
 APP_KEYPAD_TEST = 1
 APP_SD_TEST	= 1
 APP_SD_TEST	= 1
 APP_VIBRO_TEST = 1
 APP_VIBRO_TEST = 1
@@ -70,14 +69,6 @@ endif
 # that will be shown in menu
 # that will be shown in menu
 # Prefix with APP_*
 # Prefix with APP_*
 
 
-
-APP_INFRARED_MONITOR	?= 0
-ifeq ($(APP_INFRARED_MONITOR), 1)
-CFLAGS		+= -DAPP_INFRARED_MONITOR
-SRV_GUI		= 1
-endif
-
-
 APP_UNIT_TESTS ?= 0
 APP_UNIT_TESTS ?= 0
 ifeq ($(APP_UNIT_TESTS), 1)
 ifeq ($(APP_UNIT_TESTS), 1)
 CFLAGS		+= -DAPP_UNIT_TESTS
 CFLAGS		+= -DAPP_UNIT_TESTS

+ 2 - 2
applications/gui/modules/loading.c

@@ -20,10 +20,10 @@ typedef struct {
 static void loading_draw_callback(Canvas* canvas, void* _model) {
 static void loading_draw_callback(Canvas* canvas, void* _model) {
     LoadingModel* model = (LoadingModel*)_model;
     LoadingModel* model = (LoadingModel*)_model;
 
 
-    uint8_t x = 7;
-    uint8_t y = 40;
     uint8_t width = 49;
     uint8_t width = 49;
     uint8_t height = 47;
     uint8_t height = 47;
+    uint8_t x = (canvas_width(canvas) - width) / 2;
+    uint8_t y = (canvas_height(canvas) - height) / 2;
 
 
     elements_bold_rounded_frame(canvas, x, y, width, height);
     elements_bold_rounded_frame(canvas, x, y, width, height);
 
 

+ 19 - 0
applications/gui/scene_manager.c

@@ -165,6 +165,25 @@ bool scene_manager_search_and_switch_to_previous_scene(
     }
     }
 }
 }
 
 
+bool scene_manager_search_and_switch_to_previous_scene_one_of(
+    SceneManager* scene_manager,
+    const uint32_t* scene_ids,
+    size_t scene_ids_size) {
+    furi_assert(scene_manager);
+    furi_assert(scene_ids);
+    bool scene_found = false;
+
+    for(size_t i = 0; i < scene_ids_size; ++i) {
+        const uint32_t scene_id = scene_ids[i];
+        if(scene_manager_has_previous_scene(scene_manager, scene_id)) {
+            scene_manager_search_and_switch_to_previous_scene(scene_manager, scene_id);
+            scene_found = true;
+            break;
+        }
+    }
+    return scene_found;
+}
+
 bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id) {
 bool scene_manager_has_previous_scene(SceneManager* scene_manager, uint32_t scene_id) {
     furi_assert(scene_manager);
     furi_assert(scene_manager);
     bool scene_found = false;
     bool scene_found = false;

+ 14 - 0
applications/gui/scene_manager.h

@@ -5,6 +5,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <stddef.h>
 #include <stdint.h>
 #include <stdint.h>
 #include <stdbool.h>
 #include <stdbool.h>
 
 
@@ -146,6 +147,19 @@ bool scene_manager_search_and_switch_to_previous_scene(
     SceneManager* scene_manager,
     SceneManager* scene_manager,
     uint32_t scene_id);
     uint32_t scene_id);
 
 
+/** Search and switch to previous Scene, multiple choice
+ *
+ * @param      scene_manager    SceneManager instance
+ * @param      scene_ids        Array of scene IDs
+ * @param      scene_ids_size   Array of scene IDs size
+ *
+ * @return     true if one of previous scenes was found, false otherwise
+ */
+bool scene_manager_search_and_switch_to_previous_scene_one_of(
+    SceneManager* scene_manager,
+    const uint32_t* scene_ids,
+    size_t scene_ids_size);
+
 /** Clear Scene stack and switch to another Scene
 /** Clear Scene stack and switch to another Scene
  *
  *
  * @param      scene_manager  SceneManager instance
  * @param      scene_manager  SceneManager instance

+ 0 - 157
applications/infrared/helpers/infrared_parser.cpp

@@ -1,157 +0,0 @@
-
-#include "../infrared_app_signal.h"
-#include "infrared.h"
-#include "infrared/helpers/infrared_parser.h"
-#include "infrared_worker.h"
-#include "m-string.h"
-#include <flipper_format/flipper_format.h>
-#include <memory>
-#include <string>
-#include <furi_hal_infrared.h>
-
-#define TAG "InfraredParser"
-
-bool infrared_parser_save_signal(
-    FlipperFormat* ff,
-    const InfraredAppSignal& signal,
-    const std::string& name) {
-    furi_assert(ff);
-    furi_assert(!name.empty());
-
-    bool result = false;
-
-    do {
-        if(!flipper_format_write_comment_cstr(ff, "")) break;
-        if(!flipper_format_write_string_cstr(ff, "name", name.c_str())) break;
-        if(signal.is_raw()) {
-            furi_assert(signal.get_raw_signal().timings_cnt <= MAX_TIMINGS_AMOUNT);
-            auto raw_signal = signal.get_raw_signal();
-            if(!flipper_format_write_string_cstr(ff, "type", "raw")) break;
-            if(!flipper_format_write_uint32(ff, "frequency", &raw_signal.frequency, 1)) break;
-            if(!flipper_format_write_float(ff, "duty_cycle", &raw_signal.duty_cycle, 1)) break;
-            if(!flipper_format_write_uint32(ff, "data", raw_signal.timings, raw_signal.timings_cnt))
-                break;
-        } else {
-            auto parsed_signal = signal.get_message();
-            const char* protocol_name = infrared_get_protocol_name(parsed_signal.protocol);
-            if(!flipper_format_write_string_cstr(ff, "type", "parsed")) break;
-            if(!flipper_format_write_string_cstr(ff, "protocol", protocol_name)) break;
-            if(!flipper_format_write_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4))
-                break;
-            if(!flipper_format_write_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4))
-                break;
-        }
-        result = true;
-    } while(0);
-
-    return result;
-}
-
-bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name) {
-    furi_assert(ff);
-
-    bool result = false;
-    string_t read_string;
-    string_init(read_string);
-
-    do {
-        if(!flipper_format_read_string(ff, "name", read_string)) break;
-        name = string_get_cstr(read_string);
-        if(!flipper_format_read_string(ff, "type", read_string)) break;
-        if(!string_cmp_str(read_string, "raw")) {
-            uint32_t* timings = nullptr;
-            uint32_t timings_cnt = 0;
-            uint32_t frequency = 0;
-            float duty_cycle = 0;
-
-            if(!flipper_format_read_uint32(ff, "frequency", &frequency, 1)) break;
-            if(!flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1)) break;
-            if(!flipper_format_get_value_count(ff, "data", &timings_cnt)) break;
-            if(timings_cnt > MAX_TIMINGS_AMOUNT) break;
-            timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt);
-            if(flipper_format_read_uint32(ff, "data", timings, timings_cnt)) {
-                signal.set_raw_signal(timings, timings_cnt, frequency, duty_cycle);
-                result = true;
-            }
-            free(timings);
-        } else if(!string_cmp_str(read_string, "parsed")) {
-            InfraredMessage parsed_signal;
-            if(!flipper_format_read_string(ff, "protocol", read_string)) break;
-            parsed_signal.protocol = infrared_get_protocol_by_name(string_get_cstr(read_string));
-            if(!flipper_format_read_hex(ff, "address", (uint8_t*)&parsed_signal.address, 4)) break;
-            if(!flipper_format_read_hex(ff, "command", (uint8_t*)&parsed_signal.command, 4)) break;
-            if(!infrared_parser_is_parsed_signal_valid(&parsed_signal)) break;
-            signal.set_message(&parsed_signal);
-            result = true;
-        } else {
-            FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) ");
-        }
-    } while(0);
-
-    string_clear(read_string);
-    return result;
-}
-
-bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal) {
-    furi_assert(signal);
-    bool result = true;
-
-    if(!infrared_is_protocol_valid(signal->protocol)) {
-        FURI_LOG_E(TAG, "Unknown protocol");
-        result = false;
-    }
-
-    if(result) {
-        uint32_t address_length = infrared_get_protocol_address_length(signal->protocol);
-        uint32_t address_mask = (1LU << address_length) - 1;
-        if(signal->address != (signal->address & address_mask)) {
-            FURI_LOG_E(
-                TAG,
-                "Address is out of range (mask 0x%08lX): 0x%lX\r\n",
-                address_mask,
-                signal->address);
-            result = false;
-        }
-    }
-
-    if(result) {
-        uint32_t command_length = infrared_get_protocol_command_length(signal->protocol);
-        uint32_t command_mask = (1LU << command_length) - 1;
-        if(signal->command != (signal->command & command_mask)) {
-            FURI_LOG_E(
-                TAG,
-                "Command is out of range (mask 0x%08lX): 0x%lX\r\n",
-                command_mask,
-                signal->command);
-            result = false;
-        }
-    }
-
-    return result;
-}
-
-bool infrared_parser_is_raw_signal_valid(
-    uint32_t frequency,
-    float duty_cycle,
-    uint32_t timings_cnt) {
-    bool result = true;
-
-    if((frequency > INFRARED_MAX_FREQUENCY) || (frequency < INFRARED_MIN_FREQUENCY)) {
-        FURI_LOG_E(
-            TAG,
-            "Frequency is out of range (%lX - %lX): %lX",
-            INFRARED_MIN_FREQUENCY,
-            INFRARED_MAX_FREQUENCY,
-            frequency);
-        result = false;
-    } else if((duty_cycle <= 0) || (duty_cycle > 1)) {
-        FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)duty_cycle);
-        result = false;
-    } else if((timings_cnt <= 0) || (timings_cnt > MAX_TIMINGS_AMOUNT)) {
-        FURI_LOG_E(
-            TAG, "Timings amount is out of range (0 - %lX): %lX", MAX_TIMINGS_AMOUNT, timings_cnt);
-        result = false;
-    }
-
-    return result;
-}

+ 0 - 48
applications/infrared/helpers/infrared_parser.h

@@ -1,48 +0,0 @@
-/**
-  * @file infrared_parser.h
-  * Infrared: Helper file for conversion Flipper File Format
-  *     to Infrared signal class, and backwards
-  */
-#pragma once
-
-#include "../infrared_app_signal.h"
-#include <flipper_format/flipper_format.h>
-#include <string>
-
-/** Save Infrared signal into file
- *
- * @param ff - Flipper File Format instance
- * @param signal - Infrared signal to save
- * @param name - name for saved signal. Every
- *      signal on disk has name.
- */
-bool infrared_parser_save_signal(
-    FlipperFormat* ff,
-    const InfraredAppSignal& signal,
-    const std::string& name);
-
-/** Read Infrared signal from file
- *
- * @param ff - Flipper File Format instance
- * @param signal - Infrared signal to read to
- * @param name - name for saved signal. Every
- *      signal in file has name.
- */
-bool infrared_parser_read_signal(FlipperFormat* ff, InfraredAppSignal& signal, std::string& name);
-
-/** Validate parsed signal
- *
- * @signal - signal to validate
- * @retval true if valid, false otherwise
- */
-bool infrared_parser_is_parsed_signal_valid(const InfraredMessage* signal);
-
-/** Validate raw signal
- *
- * @signal - signal to validate
- * @retval true if valid, false otherwise
- */
-bool infrared_parser_is_raw_signal_valid(
-    uint32_t frequency,
-    float duty_cycle,
-    uint32_t timings_cnt);

+ 397 - 0
applications/infrared/infrared.c

@@ -0,0 +1,397 @@
+#include "infrared_i.h"
+
+#include <string.h>
+#include <dolphin/dolphin.h>
+
+static const NotificationSequence* infrared_notification_sequences[] = {
+    &sequence_success,
+    &sequence_set_only_green_255,
+    &sequence_reset_green,
+    &sequence_blink_cyan_10,
+    &sequence_blink_magenta_10};
+
+static void infrared_make_app_folder(Infrared* infrared) {
+    if(!storage_simply_mkdir(infrared->storage, INFRARED_APP_FOLDER)) {
+        dialog_message_show_storage_error(infrared->dialogs, "Cannot create\napp folder");
+    }
+}
+
+static bool infrared_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Infrared* infrared = context;
+    return scene_manager_handle_custom_event(infrared->scene_manager, event);
+}
+
+static bool infrared_back_event_callback(void* context) {
+    furi_assert(context);
+    Infrared* infrared = context;
+    return scene_manager_handle_back_event(infrared->scene_manager);
+}
+
+static void infrared_tick_event_callback(void* context) {
+    furi_assert(context);
+    Infrared* infrared = context;
+    scene_manager_handle_tick_event(infrared->scene_manager);
+}
+
+static void infrared_find_vacant_remote_name(string_t name, const char* path) {
+    Storage* storage = furi_record_open("storage");
+
+    string_t base_path;
+    string_init_set_str(base_path, path);
+
+    if(string_end_with_str_p(base_path, INFRARED_APP_EXTENSION)) {
+        size_t filename_start = string_search_rchar(base_path, '/');
+        string_left(base_path, filename_start);
+    }
+
+    string_printf(base_path, "%s/%s%s", path, string_get_cstr(name), INFRARED_APP_EXTENSION);
+
+    FS_Error status = storage_common_stat(storage, string_get_cstr(base_path), NULL);
+
+    if(status == FSE_OK) {
+        /* If the suggested name is occupied, try another one (name2, name3, etc) */
+        size_t dot = string_search_rchar(base_path, '.');
+        string_left(base_path, dot);
+
+        string_t path_temp;
+        string_init(path_temp);
+
+        uint32_t i = 1;
+        do {
+            string_printf(
+                path_temp, "%s%u%s", string_get_cstr(base_path), ++i, INFRARED_APP_EXTENSION);
+            status = storage_common_stat(storage, string_get_cstr(path_temp), NULL);
+        } while(status == FSE_OK);
+
+        string_clear(path_temp);
+
+        if(status == FSE_NOT_EXIST) {
+            string_cat_printf(name, "%u", i);
+        }
+    }
+
+    string_clear(base_path);
+    furi_record_close("storage");
+}
+
+static Infrared* infrared_alloc() {
+    Infrared* infrared = malloc(sizeof(Infrared));
+
+    string_init(infrared->file_path);
+
+    InfraredAppState* app_state = &infrared->app_state;
+    app_state->is_learning_new_remote = false;
+    app_state->is_debug_enabled = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
+    app_state->edit_target = InfraredEditTargetNone;
+    app_state->edit_mode = InfraredEditModeNone;
+    app_state->current_button_index = InfraredButtonIndexNone;
+
+    infrared->scene_manager = scene_manager_alloc(&infrared_scene_handlers, infrared);
+    infrared->view_dispatcher = view_dispatcher_alloc();
+
+    infrared->gui = furi_record_open("gui");
+
+    ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
+    view_dispatcher_attach_to_gui(view_dispatcher, infrared->gui, ViewDispatcherTypeFullscreen);
+    view_dispatcher_enable_queue(view_dispatcher);
+    view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
+    view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
+    view_dispatcher_set_tick_event_callback(view_dispatcher, infrared_tick_event_callback, 100);
+
+    infrared->storage = furi_record_open("storage");
+    infrared->dialogs = furi_record_open("dialogs");
+    infrared->notifications = furi_record_open("notification");
+
+    infrared->worker = infrared_worker_alloc();
+    infrared->remote = infrared_remote_alloc();
+    infrared->received_signal = infrared_signal_alloc();
+    infrared->brute_force = infrared_brute_force_alloc();
+
+    infrared->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        view_dispatcher, InfraredViewSubmenu, submenu_get_view(infrared->submenu));
+
+    infrared->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        view_dispatcher, InfraredViewTextInput, text_input_get_view(infrared->text_input));
+
+    infrared->dialog_ex = dialog_ex_alloc();
+    view_dispatcher_add_view(
+        view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex));
+
+    infrared->button_menu = button_menu_alloc();
+    view_dispatcher_add_view(
+        view_dispatcher, InfraredViewButtonMenu, button_menu_get_view(infrared->button_menu));
+
+    infrared->popup = popup_alloc();
+    view_dispatcher_add_view(view_dispatcher, InfraredViewPopup, popup_get_view(infrared->popup));
+
+    infrared->view_stack = view_stack_alloc();
+    view_dispatcher_add_view(
+        view_dispatcher, InfraredViewStack, view_stack_get_view(infrared->view_stack));
+
+    if(app_state->is_debug_enabled) {
+        infrared->debug_view = infrared_debug_view_alloc();
+        view_dispatcher_add_view(
+            view_dispatcher,
+            InfraredViewDebugView,
+            infrared_debug_view_get_view(infrared->debug_view));
+    }
+
+    infrared->button_panel = button_panel_alloc();
+    infrared->loading = loading_alloc();
+    infrared->progress = infrared_progress_view_alloc();
+
+    return infrared;
+}
+
+static void infrared_free(Infrared* infrared) {
+    furi_assert(infrared);
+    ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
+    InfraredAppState* app_state = &infrared->app_state;
+
+    view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu);
+    submenu_free(infrared->submenu);
+
+    view_dispatcher_remove_view(view_dispatcher, InfraredViewTextInput);
+    text_input_free(infrared->text_input);
+
+    view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx);
+    dialog_ex_free(infrared->dialog_ex);
+
+    view_dispatcher_remove_view(view_dispatcher, InfraredViewButtonMenu);
+    button_menu_free(infrared->button_menu);
+
+    view_dispatcher_remove_view(view_dispatcher, InfraredViewPopup);
+    popup_free(infrared->popup);
+
+    view_dispatcher_remove_view(view_dispatcher, InfraredViewStack);
+    view_stack_free(infrared->view_stack);
+
+    if(app_state->is_debug_enabled) {
+        view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView);
+        infrared_debug_view_free(infrared->debug_view);
+    }
+
+    button_panel_free(infrared->button_panel);
+    loading_free(infrared->loading);
+    infrared_progress_view_free(infrared->progress);
+
+    view_dispatcher_free(view_dispatcher);
+    scene_manager_free(infrared->scene_manager);
+
+    infrared_brute_force_free(infrared->brute_force);
+    infrared_signal_free(infrared->received_signal);
+    infrared_remote_free(infrared->remote);
+    infrared_worker_free(infrared->worker);
+
+    furi_record_close("gui");
+    infrared->gui = NULL;
+
+    furi_record_close("notification");
+    infrared->notifications = NULL;
+
+    furi_record_close("dialogs");
+    infrared->dialogs = NULL;
+
+    furi_record_close("gui");
+    infrared->gui = NULL;
+
+    string_clear(infrared->file_path);
+
+    free(infrared);
+}
+
+bool infrared_add_remote_with_button(
+    Infrared* infrared,
+    const char* button_name,
+    InfraredSignal* signal) {
+    InfraredRemote* remote = infrared->remote;
+
+    string_t new_name, new_path;
+    string_init_set_str(new_name, INFRARED_DEFAULT_REMOTE_NAME);
+    string_init_set_str(new_path, INFRARED_APP_FOLDER);
+
+    infrared_find_vacant_remote_name(new_name, string_get_cstr(new_path));
+    string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION);
+
+    infrared_remote_reset(remote);
+    infrared_remote_set_name(remote, string_get_cstr(new_name));
+    infrared_remote_set_path(remote, string_get_cstr(new_path));
+
+    string_clear(new_name);
+    string_clear(new_path);
+    return infrared_remote_add_button(remote, button_name, signal);
+}
+
+bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
+    InfraredRemote* remote = infrared->remote;
+    const char* remote_path = infrared_remote_get_path(remote);
+
+    if(!strcmp(infrared_remote_get_name(remote), name)) {
+        return true;
+    }
+
+    string_t new_name;
+    string_init_set_str(new_name, name);
+
+    infrared_find_vacant_remote_name(new_name, remote_path);
+
+    string_t new_path;
+    string_init_set(new_path, infrared_remote_get_path(remote));
+    if(string_end_with_str_p(new_path, INFRARED_APP_EXTENSION)) {
+        size_t filename_start = string_search_rchar(new_path, '/');
+        string_left(new_path, filename_start);
+    }
+    string_cat_printf(new_path, "/%s%s", string_get_cstr(new_name), INFRARED_APP_EXTENSION);
+
+    Storage* storage = furi_record_open("storage");
+
+    FS_Error status = storage_common_rename(
+        storage, infrared_remote_get_path(remote), string_get_cstr(new_path));
+    infrared_remote_set_name(remote, string_get_cstr(new_name));
+
+    string_clear(new_name);
+    string_clear(new_path);
+
+    furi_record_close("storage");
+    return (status == FSE_OK || status == FSE_EXIST);
+}
+
+void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
+    if(infrared_signal_is_raw(signal)) {
+        InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
+        infrared_worker_set_raw_signal(infrared->worker, raw->timings, raw->timings_size);
+    } else {
+        InfraredMessage* message = infrared_signal_get_message(signal);
+        infrared_worker_set_decoded_signal(infrared->worker, message);
+    }
+
+    DOLPHIN_DEED(DolphinDeedIrSend);
+    infrared_worker_tx_start(infrared->worker);
+}
+
+void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
+    furi_assert(button_index < infrared_remote_get_button_count(infrared->remote));
+
+    InfraredRemoteButton* button = infrared_remote_get_button(infrared->remote, button_index);
+    InfraredSignal* signal = infrared_remote_button_get_signal(button);
+
+    infrared_tx_start_signal(infrared, signal);
+}
+
+void infrared_tx_start_received(Infrared* infrared) {
+    infrared_tx_start_signal(infrared, infrared->received_signal);
+}
+
+void infrared_tx_stop(Infrared* infrared) {
+    infrared_worker_tx_stop(infrared->worker);
+}
+
+void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {
+    va_list args;
+    va_start(args, text);
+
+    vsnprintf(infrared->text_store[bank], INFRARED_TEXT_STORE_SIZE, text, args);
+
+    va_end(args);
+}
+
+void infrared_text_store_clear(Infrared* infrared, uint32_t bank) {
+    memset(infrared->text_store[bank], 0, INFRARED_TEXT_STORE_SIZE);
+}
+
+void infrared_play_notification_message(Infrared* infrared, uint32_t message) {
+    furi_assert(message < sizeof(infrared_notification_sequences) / sizeof(NotificationSequence*));
+    notification_message(infrared->notifications, infrared_notification_sequences[message]);
+}
+
+void infrared_show_loading_popup(Infrared* infrared, bool show) {
+    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
+    ViewStack* view_stack = infrared->view_stack;
+    Loading* loading = infrared->loading;
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
+        view_stack_add_view(view_stack, loading_get_view(loading));
+    } else {
+        view_stack_remove_view(view_stack, loading_get_view(loading));
+        // Restore default timer priority
+        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
+    }
+}
+
+void infrared_signal_sent_callback(void* context) {
+    furi_assert(context);
+    Infrared* infrared = context;
+    infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkSend);
+}
+
+void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
+    furi_assert(context);
+    Infrared* infrared = context;
+
+    if(infrared_worker_signal_is_decoded(received_signal)) {
+        infrared_signal_set_message(
+            infrared->received_signal, infrared_worker_get_decoded_signal(received_signal));
+    } else {
+        const uint32_t* timings;
+        size_t timings_size;
+        infrared_worker_get_raw_signal(received_signal, &timings, &timings_size);
+        infrared_signal_set_raw_signal(
+            infrared->received_signal,
+            timings,
+            timings_size,
+            INFRARED_COMMON_CARRIER_FREQUENCY,
+            INFRARED_COMMON_DUTY_CYCLE);
+    }
+
+    view_dispatcher_send_custom_event(
+        infrared->view_dispatcher, InfraredCustomEventTypeSignalReceived);
+}
+
+void infrared_text_input_callback(void* context) {
+    furi_assert(context);
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(
+        infrared->view_dispatcher, InfraredCustomEventTypeTextEditDone);
+}
+
+void infrared_popup_timeout_callback(void* context) {
+    furi_assert(context);
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(
+        infrared->view_dispatcher, InfraredCustomEventTypePopupTimeout);
+}
+
+int32_t infrared_app(void* p) {
+    Infrared* infrared = infrared_alloc();
+
+    infrared_make_app_folder(infrared);
+
+    bool is_remote_loaded = false;
+
+    if(p) {
+        string_set_str(infrared->file_path, (const char*)p);
+        is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
+        if(!is_remote_loaded) {
+            dialog_message_show_storage_error(
+                infrared->dialogs, "Failed to load\nselected remote");
+            return -1;
+        }
+    }
+
+    if(is_remote_loaded) {
+        scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
+    } else {
+        scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);
+    }
+
+    view_dispatcher_run(infrared->view_dispatcher);
+
+    infrared_free(infrared);
+    return 0;
+}

+ 3 - 0
applications/infrared/infrared.h

@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct Infrared Infrared;

+ 0 - 250
applications/infrared/infrared_app.cpp

@@ -1,250 +0,0 @@
-#include "infrared_app.h"
-#include "m-string.h"
-#include <infrared_worker.h>
-#include <furi.h>
-#include <gui/gui.h>
-#include <input/input.h>
-#include <stdio.h>
-#include <callback-connector.h>
-
-int32_t InfraredApp::run(void* args) {
-    InfraredAppEvent event;
-    bool consumed;
-    bool exit = false;
-
-    if(args) {
-        string_t path;
-        string_init_set_str(path, (char*)args);
-        if(string_end_with_str_p(path, InfraredApp::infrared_extension)) {
-            bool result = remote_manager.load(path);
-            if(result) {
-                current_scene = InfraredApp::Scene::Remote;
-            } else {
-                printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path));
-                return -1;
-            }
-        }
-        string_clear(path);
-    }
-
-    scenes[current_scene]->on_enter(this);
-
-    while(!exit) {
-        view_manager.receive_event(&event);
-
-        if(event.type == InfraredAppEvent::Type::Exit) break;
-
-        consumed = scenes[current_scene]->on_event(this, &event);
-
-        if(!consumed) {
-            if(event.type == InfraredAppEvent::Type::Back) {
-                exit = switch_to_previous_scene();
-            }
-        }
-    };
-
-    scenes[current_scene]->on_exit(this);
-
-    return 0;
-};
-
-InfraredApp::InfraredApp() {
-    furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size());
-    string_init_set_str(file_path, InfraredApp::infrared_directory);
-    notification = static_cast<NotificationApp*>(furi_record_open("notification"));
-    dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs"));
-    infrared_worker = infrared_worker_alloc();
-}
-
-InfraredApp::~InfraredApp() {
-    infrared_worker_free(infrared_worker);
-    furi_record_close("notification");
-    furi_record_close("dialogs");
-    string_clear(file_path);
-    for(auto& [key, scene] : scenes) delete scene;
-}
-
-InfraredAppViewManager* InfraredApp::get_view_manager() {
-    return &view_manager;
-}
-
-void InfraredApp::set_learn_new_remote(bool value) {
-    learn_new_remote = value;
-}
-
-bool InfraredApp::get_learn_new_remote() {
-    return learn_new_remote;
-}
-
-void InfraredApp::switch_to_next_scene(Scene next_scene) {
-    previous_scenes_list.push_front(current_scene);
-    switch_to_next_scene_without_saving(next_scene);
-}
-
-void InfraredApp::switch_to_next_scene_without_saving(Scene next_scene) {
-    if(next_scene != Scene::Exit) {
-        scenes[current_scene]->on_exit(this);
-        current_scene = next_scene;
-        scenes[current_scene]->on_enter(this);
-        view_manager.clear_events();
-    }
-}
-
-void InfraredApp::search_and_switch_to_previous_scene(
-    const std::initializer_list<Scene>& scenes_list) {
-    Scene previous_scene = Scene::Start;
-    bool scene_found = false;
-
-    while(!scene_found) {
-        previous_scene = get_previous_scene();
-
-        if(previous_scene == Scene::Exit) break;
-
-        for(Scene element : scenes_list) {
-            if(previous_scene == element) {
-                scene_found = true;
-                break;
-            }
-        }
-    }
-
-    if(previous_scene == Scene::Exit) {
-        InfraredAppEvent event;
-        event.type = InfraredAppEvent::Type::Exit;
-        view_manager.send_event(&event);
-    } else {
-        scenes[current_scene]->on_exit(this);
-        current_scene = previous_scene;
-        scenes[current_scene]->on_enter(this);
-        view_manager.clear_events();
-    }
-}
-
-bool InfraredApp::switch_to_previous_scene(uint8_t count) {
-    Scene previous_scene = Scene::Start;
-
-    for(uint8_t i = 0; i < count; i++) previous_scene = get_previous_scene();
-
-    if(previous_scene == Scene::Exit) return true;
-
-    scenes[current_scene]->on_exit(this);
-    current_scene = previous_scene;
-    scenes[current_scene]->on_enter(this);
-    view_manager.clear_events();
-    return false;
-}
-
-InfraredApp::Scene InfraredApp::get_previous_scene() {
-    Scene scene = Scene::Exit;
-
-    if(!previous_scenes_list.empty()) {
-        scene = previous_scenes_list.front();
-        previous_scenes_list.pop_front();
-    }
-
-    return scene;
-}
-
-InfraredAppRemoteManager* InfraredApp::get_remote_manager() {
-    return &remote_manager;
-}
-
-void InfraredApp::set_text_store(uint8_t index, const char* text...) {
-    furi_check(index < text_store_max);
-
-    va_list args;
-    va_start(args, text);
-
-    vsnprintf(text_store[index], text_store_size, text, args);
-
-    va_end(args);
-}
-
-char* InfraredApp::get_text_store(uint8_t index) {
-    furi_check(index < text_store_max);
-
-    return text_store[index];
-}
-
-uint8_t InfraredApp::get_text_store_size() {
-    return text_store_size;
-}
-
-void InfraredApp::text_input_callback(void* context) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-    event.type = InfraredAppEvent::Type::TextEditDone;
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredApp::popup_callback(void* context) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-    event.type = InfraredAppEvent::Type::PopupTimer;
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredApp::set_edit_element(InfraredApp::EditElement value) {
-    element = value;
-}
-
-InfraredApp::EditElement InfraredApp::get_edit_element(void) {
-    return element;
-}
-
-void InfraredApp::set_edit_action(InfraredApp::EditAction value) {
-    action = value;
-}
-
-InfraredApp::EditAction InfraredApp::get_edit_action(void) {
-    return action;
-}
-
-void InfraredApp::set_current_button(int value) {
-    current_button = value;
-}
-
-int InfraredApp::get_current_button() {
-    return current_button;
-}
-
-void InfraredApp::notify_success() {
-    notification_message(notification, &sequence_success);
-}
-
-void InfraredApp::notify_blink_read() {
-    notification_message(notification, &sequence_blink_cyan_10);
-}
-
-void InfraredApp::notify_blink_send() {
-    notification_message(notification, &sequence_blink_magenta_10);
-}
-
-DialogsApp* InfraredApp::get_dialogs() {
-    return dialogs;
-}
-
-void InfraredApp::notify_green_on() {
-    notification_message(notification, &sequence_set_only_green_255);
-}
-
-void InfraredApp::notify_green_off() {
-    notification_message(notification, &sequence_reset_green);
-}
-
-InfraredWorker* InfraredApp::get_infrared_worker() {
-    return infrared_worker;
-}
-
-const InfraredAppSignal& InfraredApp::get_received_signal() const {
-    return received_signal;
-}
-
-void InfraredApp::set_received_signal(const InfraredAppSignal& signal) {
-    received_signal = signal;
-}
-
-void InfraredApp::signal_sent_callback(void* context) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    app->notify_blink_send();
-}

+ 0 - 326
applications/infrared/infrared_app.h

@@ -1,326 +0,0 @@
-/**
-  * @file infrared_app.h
-  * Infrared: Main infrared application class
-  */
-#pragma once
-#include <map>
-#include <infrared.h>
-#include <furi.h>
-#include <forward_list>
-#include <stdint.h>
-#include <notification/notification_messages.h>
-#include <dialogs/dialogs.h>
-#include <infrared_worker.h>
-
-#include "scene/infrared_app_scene.h"
-#include "scene/infrared_app_scene.h"
-#include "infrared_app_view_manager.h"
-#include "infrared_app_remote_manager.h"
-#include "infrared_app_view_manager.h"
-
-/** Main Infrared application class */
-class InfraredApp {
-public:
-    /** Enum to save scene state: edit element */
-    enum class EditElement : uint8_t {
-        Button,
-        Remote,
-    };
-    /** Enum to save scene state: edit action */
-    enum class EditAction : uint8_t {
-        Rename,
-        Delete,
-    };
-    /** List of scenes for Infrared application */
-    enum class Scene : uint8_t {
-        Exit,
-        Start,
-        Universal,
-        UniversalTV,
-        UniversalAudio,
-        UniversalAirConditioner,
-        Learn,
-        LearnSuccess,
-        LearnEnterName,
-        LearnDone,
-        AskBack,
-        Remote,
-        RemoteList,
-        Edit,
-        EditKeySelect,
-        EditRename,
-        EditDelete,
-        EditRenameDone,
-        EditDeleteDone,
-    };
-
-    /** Start application
- *
- * @param args - application arguments.
- *      Allowed argument is path to remote file.
- * @retval 0 on success, error code otherwise
- */
-    int32_t run(void* args);
-
-    /** Switch to next scene. Put current scene number on stack.
- * Doesn't save scene state.
- *
- * @param index - next scene index
- */
-    void switch_to_next_scene(Scene index);
-
-    /** Switch to next scene, but don't put current scene on
- * stack. Thus calling switch_to_previous_scene() doesn't return
- * to current scene.
- *
- * @param index - next scene index
- */
-    void switch_to_next_scene_without_saving(Scene index);
-
-    /** Switch to previous scene. Pop scenes from stack and switch to last one.
- *
- * @param count - how many scenes should be popped
- * @retval false on failed, true on success
- */
-    bool switch_to_previous_scene(uint8_t count = 1);
-
-    /** Get previous scene in scene stack
- *
- * @retval previous scene
- */
-    Scene get_previous_scene();
-
-    /** Get view manager instance
- *
- * @retval view manager instance
- */
-    InfraredAppViewManager* get_view_manager();
-
-    /** Set one of text stores
- *
- * @param index - index of text store
- * @param text - text to set
- */
-    void set_text_store(uint8_t index, const char* text...);
-
-    /** Get value in text store
- *
- * @param index - index of text store
- * @retval value in text_store
- */
-    char* get_text_store(uint8_t index);
-
-    /** Get text store size
- *
- * @retval size of text store
- */
-    uint8_t get_text_store_size();
-
-    /** Get remote manager instance
- *
- * @retval remote manager instance
- */
-    InfraredAppRemoteManager* get_remote_manager();
-
-    /** Get infrared worker instance
- *
- * @retval infrared worker instance
- */
-    InfraredWorker* get_infrared_worker();
-
-    /** Get signal, previously got on Learn scene
- *
- * @retval received signal
- */
-    const InfraredAppSignal& get_received_signal() const;
-
-    /** Set received signal
- *
- * @param signal - signal
- */
-    void set_received_signal(const InfraredAppSignal& signal);
-
-    /** Switch to previous scene in one of provided in list.
- * Pop scene stack, and find first scene from list.
- *
- * @param scenes_list - list of scenes
- */
-    void search_and_switch_to_previous_scene(const std::initializer_list<Scene>& scenes_list);
-
-    /** Set edit element value. It is used on edit scene to determine
- * what should be deleted - remote or button.
- *
- * @param value - value to set
- */
-    void set_edit_element(EditElement value);
-
-    /** Get edit element
- *
- * @retval edit element value
- */
-    EditElement get_edit_element(void);
-
-    /** Set edit action value. It is used on edit scene to determine
- * what action to perform - deletion or renaming.
- *
- * @param value - value to set
- */
-    void set_edit_action(EditAction value);
-
-    /** Get edit action
- *
- * @retval edit action value
- */
-    EditAction get_edit_action(void);
-
-    /** Get state of learning new signal.
- * Adding new remote with 1 button from start scene and
- * learning 1 additional button to remote have very similar
- * flow, so they are joined. Difference in flow is handled
- * by this boolean flag.
- *
- * @retval false if flow is in learning new remote, true if
- *      adding signal to created remote
- *
- */
-    bool get_learn_new_remote();
-
-    /** Set state of learning new signal.
- * Adding new remote with 1 button from start scene and
- * learning 1 additional button to remote have very similar
- * flow, so they are joined. Difference in flow is handled
- * by this boolean flag.
- *
- * @param value - false if flow is in learning new remote, true if
- *      adding signal to created remote
- */
-    void set_learn_new_remote(bool value);
-
-    /** Button is not assigned value
- */
-    enum : int {
-        ButtonNA = -1,
-    };
-
-    /** Get current button index
- *
- * @retval current button index
- */
-    int get_current_button();
-
-    /** Set current button index
- *
- * @param current button index
- */
-    void set_current_button(int value);
-
-    /** Play success notification */
-    void notify_success();
-    /** Play red blink notification */
-    void notify_blink_read();
-    /** Light green */
-    void notify_green_on();
-    /** Disable green light */
-    void notify_green_off();
-    /** Blink on send */
-    void notify_blink_send();
-
-    /** Get Dialogs instance */
-    DialogsApp* get_dialogs();
-
-    /** Text input callback
- *
- * @param context - context to pass to callback
- */
-    static void text_input_callback(void* context);
-
-    /** Popup callback
- *
- * @param context - context to pass to callback
- */
-    static void popup_callback(void* context);
-
-    /** Signal sent callback
- *
- * @param context - context to pass to callback
- */
-    static void signal_sent_callback(void* context);
-
-    /** Main class constructor, initializes all critical objects */
-    InfraredApp();
-    /** Main class destructor, deinitializes all critical objects */
-    ~InfraredApp();
-
-    string_t file_path;
-
-    /** Path to Infrared directory */
-    static constexpr const char* infrared_directory = "/any/infrared";
-    /** Infrared files extension (remote files and universal databases) */
-    static constexpr const char* infrared_extension = ".ir";
-    /** Max Raw timings in signal */
-    static constexpr const uint32_t max_raw_timings_in_signal = 512;
-    /** Max line length in Infrared file */
-    static constexpr const uint32_t max_line_length =
-        (9 + 1) * InfraredApp::max_raw_timings_in_signal + 100;
-
-private:
-    /** Text store size */
-    static constexpr const uint8_t text_store_size = 128;
-    /** Amount of text stores */
-    static constexpr const uint8_t text_store_max = 2;
-    /** Store text here, for some views, because they doesn't
- * hold ownership of text */
-    char text_store[text_store_max][text_store_size + 1];
-    /**
- * Flag to control adding new signal flow.
- * Adding new remote with 1 button from start scene and
- * learning 1 additional button to remote have very similar
- * flow, so they are joined. Difference in flow is handled
- * by this boolean flag.
- */
-    bool learn_new_remote;
-    /** Value to control edit scene */
-    EditElement element;
-    /** Value to control edit scene */
-    EditAction action;
-    /** Selected button index */
-    uint32_t current_button;
-
-    /** Notification instance */
-    NotificationApp* notification;
-    /** Dialogs instance */
-    DialogsApp* dialogs;
-    /** View manager instance */
-    InfraredAppViewManager view_manager;
-    /** Remote manager instance */
-    InfraredAppRemoteManager remote_manager;
-    /** Infrared worker instance */
-    InfraredWorker* infrared_worker;
-    /** Signal received on Learn scene */
-    InfraredAppSignal received_signal;
-
-    /** Stack of previous scenes */
-    std::forward_list<Scene> previous_scenes_list;
-    /** Now acting scene */
-    Scene current_scene = Scene::Start;
-
-    /** Map of index/scene objects */
-    std::map<Scene, InfraredAppScene*> scenes = {
-        {Scene::Start, new InfraredAppSceneStart()},
-        {Scene::Universal, new InfraredAppSceneUniversal()},
-        {Scene::UniversalTV, new InfraredAppSceneUniversalTV()},
-        {Scene::Learn, new InfraredAppSceneLearn()},
-        {Scene::LearnSuccess, new InfraredAppSceneLearnSuccess()},
-        {Scene::LearnEnterName, new InfraredAppSceneLearnEnterName()},
-        {Scene::LearnDone, new InfraredAppSceneLearnDone()},
-        {Scene::AskBack, new InfraredAppSceneAskBack()},
-        {Scene::Remote, new InfraredAppSceneRemote()},
-        {Scene::RemoteList, new InfraredAppSceneRemoteList()},
-        {Scene::Edit, new InfraredAppSceneEdit()},
-        {Scene::EditKeySelect, new InfraredAppSceneEditKeySelect()},
-        {Scene::EditRename, new InfraredAppSceneEditRename()},
-        {Scene::EditDelete, new InfraredAppSceneEditDelete()},
-        {Scene::EditRenameDone, new InfraredAppSceneEditRenameDone()},
-        {Scene::EditDeleteDone, new InfraredAppSceneEditDeleteDone()},
-    };
-};

+ 0 - 93
applications/infrared/infrared_app_brute_force.cpp

@@ -1,93 +0,0 @@
-
-#include "helpers/infrared_parser.h"
-#include "infrared_app_brute_force.h"
-#include "infrared_app_signal.h"
-#include <memory>
-#include <m-string.h>
-#include <furi.h>
-
-void InfraredAppBruteForce::add_record(int index, const char* name) {
-    records[name].index = index;
-    records[name].amount = 0;
-}
-
-bool InfraredAppBruteForce::calculate_messages() {
-    bool result = false;
-
-    Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-    FlipperFormat* ff = flipper_format_file_alloc(storage);
-    result = flipper_format_file_open_existing(ff, universal_db_filename);
-
-    if(result) {
-        InfraredAppSignal signal;
-
-        string_t signal_name;
-        string_init(signal_name);
-        while(flipper_format_read_string(ff, "name", signal_name)) {
-            auto element = records.find(string_get_cstr(signal_name));
-            if(element != records.cend()) {
-                ++element->second.amount;
-            }
-        }
-        string_clear(signal_name);
-    }
-
-    flipper_format_free(ff);
-    furi_record_close("storage");
-    return result;
-}
-
-void InfraredAppBruteForce::stop_bruteforce() {
-    furi_assert((current_record.size()));
-
-    if(current_record.size()) {
-        furi_assert(ff);
-        current_record.clear();
-        flipper_format_free(ff);
-        furi_record_close("storage");
-    }
-}
-
-bool InfraredAppBruteForce::send_next_bruteforce(void) {
-    furi_assert(current_record.size());
-    furi_assert(ff);
-
-    InfraredAppSignal signal;
-    std::string signal_name;
-    bool result = false;
-    do {
-        result = infrared_parser_read_signal(ff, signal, signal_name);
-    } while(result && current_record.compare(signal_name));
-
-    if(result) {
-        signal.transmit();
-    }
-    return result;
-}
-
-bool InfraredAppBruteForce::start_bruteforce(int index, int& record_amount) {
-    bool result = false;
-    record_amount = 0;
-
-    for(const auto& it : records) {
-        if(it.second.index == index) {
-            record_amount = it.second.amount;
-            if(record_amount) {
-                current_record = it.first;
-            }
-            break;
-        }
-    }
-
-    if(record_amount) {
-        Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-        ff = flipper_format_file_alloc(storage);
-        result = flipper_format_file_open_existing(ff, universal_db_filename);
-        if(!result) {
-            flipper_format_free(ff);
-            furi_record_close("storage");
-        }
-    }
-
-    return result;
-}

+ 0 - 67
applications/infrared/infrared_app_brute_force.h

@@ -1,67 +0,0 @@
-/**
-  * @file infrared_app_brute_force.h
-  * Infrared: Brute Force class description
-  */
-#pragma once
-
-#include <unordered_map>
-#include <memory>
-#include <flipper_format/flipper_format.h>
-
-/** Class handles brute force mechanic */
-class InfraredAppBruteForce {
-    /** Universal database filename */
-    const char* universal_db_filename;
-
-    /** Current record name (POWER, MUTE, VOL+, etc).
-     * This is the name of signal to brute force. */
-    std::string current_record;
-
-    /** Flipper File Format instance */
-    FlipperFormat* ff;
-
-    /** Data about every record - index in button panel view
-     * and amount of signals, which is need for correct
-     * progress bar displaying. */
-    typedef struct {
-        /** Index of record in button panel view model */
-        int index;
-        /** Amount of signals of that type (POWER, MUTE, etc) */
-        int amount;
-    } Record;
-
-    /** Container to hold Record info.
-     * 'key' is record name, because we have to search by both, index and name,
-     * but index search has place once per button press, and should not be
-     * noticed, but name search should occur during entering universal menu,
-     * and will go through container for every record in file, that's why
-     * more critical to have faster search by record name.
-     */
-    std::unordered_map<std::string, Record> records;
-
-public:
-    /** Calculate messages. Walk through the file ('universal_db_name')
-     * and calculate amount of records of certain type. */
-    bool calculate_messages();
-
-    /** Start brute force */
-    bool start_bruteforce(int index, int& record_amount);
-
-    /** Stop brute force */
-    void stop_bruteforce();
-
-    /** Send next signal during brute force */
-    bool send_next_bruteforce();
-
-    /** Add record to container of records */
-    void add_record(int index, const char* name);
-
-    /** Initialize class, set db file */
-    InfraredAppBruteForce(const char* filename)
-        : universal_db_filename(filename) {
-    }
-
-    /** Deinitialize class */
-    ~InfraredAppBruteForce() {
-    }
-};

+ 0 - 48
applications/infrared/infrared_app_event.h

@@ -1,48 +0,0 @@
-/**
-  * @file infrared_app_event.h
-  * Infrared: Scene events description
-  */
-#pragma once
-#include <infrared.h>
-#include <gui/modules/dialog_ex.h>
-
-/** Infrared events class */
-class InfraredAppEvent {
-public:
-    /** Type of event enum */
-    enum class Type : uint8_t {
-        /** Tick event come after no other events came in 100 ms */
-        Tick,
-        /** Exit application event */
-        Exit,
-        /** Back event */
-        Back,
-        /** Menu selected event type. Provided with payload value. */
-        MenuSelected,
-        /** Button press event. Need for continuous signal sending. */
-        MenuSelectedPress,
-        /** Button release event. Need for continuous signal sending. */
-        MenuSelectedRelease,
-        /** Events from DialogEx view module */
-        DialogExSelected,
-        /** Infrared signal received event */
-        InfraredMessageReceived,
-        /** Text edit done event */
-        TextEditDone,
-        /** Popup timer finished event */
-        PopupTimer,
-        /** Button panel pressed event */
-        ButtonPanelPressed,
-    };
-
-    union {
-        int32_t dummy;
-        /** Menu selected event type payload. Selected index. */
-        int32_t menu_index;
-        /** DialogEx view module event type payload */
-        DialogExResult dialog_ex_result;
-    } payload;
-
-    /** Type of event */
-    Type type;
-};

+ 0 - 266
applications/infrared/infrared_app_remote_manager.cpp

@@ -1,266 +0,0 @@
-#include "m-string.h"
-#include "storage/filesystem_api_defines.h"
-#include <flipper_format/flipper_format.h>
-#include "infrared_app_remote_manager.h"
-#include "infrared/helpers/infrared_parser.h"
-#include "infrared/infrared_app_signal.h"
-
-#include <utility>
-
-#include <infrared.h>
-#include <cstdio>
-#include <furi.h>
-#include <gui/modules/button_menu.h>
-#include <storage/storage.h>
-#include "infrared_app.h"
-#include <toolbox/path.h>
-
-static const char* default_remote_name = "remote";
-
-void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) {
-    Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-
-    string_t base_path;
-    string_init_set(base_path, path);
-
-    if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) {
-        size_t filename_start = string_search_rchar(base_path, '/');
-        string_left(base_path, filename_start);
-    }
-
-    string_printf(
-        base_path,
-        "%s/%s%s",
-        string_get_cstr(path),
-        string_get_cstr(name),
-        InfraredApp::infrared_extension);
-
-    FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL);
-
-    if(error == FSE_OK) {
-        /* if suggested name is occupied, try another one (name2, name3, etc) */
-        size_t dot = string_search_rchar(base_path, '.');
-        string_left(base_path, dot);
-
-        string_t path_temp;
-        string_init(path_temp);
-
-        uint32_t i = 1;
-        do {
-            string_printf(
-                path_temp,
-                "%s%u%s",
-                string_get_cstr(base_path),
-                ++i,
-                InfraredApp::infrared_extension);
-            error = storage_common_stat(storage, string_get_cstr(path_temp), NULL);
-        } while(error == FSE_OK);
-
-        string_clear(path_temp);
-
-        if(error == FSE_NOT_EXIST) {
-            string_cat_printf(name, "%u", i);
-        }
-    }
-
-    string_clear(base_path);
-    furi_record_close("storage");
-}
-
-bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) {
-    remote->buttons.emplace_back(button_name, signal);
-    return store();
-}
-
-bool InfraredAppRemoteManager::add_remote_with_button(
-    const char* button_name,
-    const InfraredAppSignal& signal) {
-    furi_check(button_name != nullptr);
-
-    string_t new_name;
-    string_init_set_str(new_name, default_remote_name);
-
-    string_t new_path;
-    string_init_set_str(new_path, InfraredApp::infrared_directory);
-
-    find_vacant_remote_name(new_name, new_path);
-
-    string_cat_printf(
-        new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension);
-
-    remote = std::make_unique<InfraredAppRemote>(new_path);
-    remote->name = std::string(string_get_cstr(new_name));
-
-    string_clear(new_path);
-    string_clear(new_name);
-
-    return add_button(button_name, signal);
-}
-
-std::vector<std::string> InfraredAppRemoteManager::get_button_list(void) const {
-    std::vector<std::string> name_vector;
-    name_vector.reserve(remote->buttons.size());
-
-    for(const auto& it : remote->buttons) {
-        name_vector.emplace_back(it.name);
-    }
-
-    // copy elision
-    return name_vector;
-}
-
-const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) const {
-    furi_check(remote.get() != nullptr);
-    auto& buttons = remote->buttons;
-    furi_check(index < buttons.size());
-
-    return buttons.at(index).signal;
-}
-
-bool InfraredAppRemoteManager::delete_remote() {
-    Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-
-    FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path));
-    reset_remote();
-
-    furi_record_close("storage");
-    return (error == FSE_OK || error == FSE_NOT_EXIST);
-}
-
-void InfraredAppRemoteManager::reset_remote() {
-    remote.reset();
-}
-
-bool InfraredAppRemoteManager::delete_button(uint32_t index) {
-    furi_check(remote.get() != nullptr);
-    auto& buttons = remote->buttons;
-    furi_check(index < buttons.size());
-
-    buttons.erase(buttons.begin() + index);
-    return store();
-}
-
-std::string InfraredAppRemoteManager::get_button_name(uint32_t index) {
-    furi_check(remote.get() != nullptr);
-    auto& buttons = remote->buttons;
-    furi_check(index < buttons.size());
-    return buttons[index].name.c_str();
-}
-
-std::string InfraredAppRemoteManager::get_remote_name() {
-    return remote.get() ? remote->name : std::string();
-}
-
-bool InfraredAppRemoteManager::rename_remote(const char* str) {
-    furi_check(str != nullptr);
-    furi_check(remote.get() != nullptr);
-    furi_check(!string_empty_p(remote->path));
-
-    if(!remote->name.compare(str)) {
-        return true;
-    }
-
-    string_t new_name;
-    string_init_set_str(new_name, str);
-    find_vacant_remote_name(new_name, remote->path);
-
-    string_t new_path;
-    string_init_set(new_path, remote->path);
-    if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) {
-        size_t filename_start = string_search_rchar(new_path, '/');
-        string_left(new_path, filename_start);
-    }
-    string_cat_printf(
-        new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension);
-
-    Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-
-    FS_Error error =
-        storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path));
-    remote->name = std::string(string_get_cstr(new_name));
-
-    string_clear(new_name);
-    string_clear(new_path);
-
-    furi_record_close("storage");
-    return (error == FSE_OK || error == FSE_EXIST);
-}
-
-bool InfraredAppRemoteManager::rename_button(uint32_t index, const char* str) {
-    furi_check(remote.get() != nullptr);
-    auto& buttons = remote->buttons;
-    furi_check(index < buttons.size());
-
-    buttons[index].name = str;
-    return store();
-}
-
-size_t InfraredAppRemoteManager::get_number_of_buttons() {
-    furi_check(remote.get() != nullptr);
-    return remote->buttons.size();
-}
-
-bool InfraredAppRemoteManager::store(void) {
-    bool result = false;
-    Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-
-    if(!storage_simply_mkdir(storage, InfraredApp::infrared_directory)) return false;
-
-    FlipperFormat* ff = flipper_format_file_alloc(storage);
-
-    FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path));
-    result = flipper_format_file_open_always(ff, string_get_cstr(remote->path));
-    if(result) {
-        result = flipper_format_write_header_cstr(ff, "IR signals file", 1);
-    }
-    if(result) {
-        for(const auto& button : remote->buttons) {
-            result = infrared_parser_save_signal(ff, button.signal, button.name.c_str());
-            if(!result) {
-                break;
-            }
-        }
-    }
-
-    flipper_format_free(ff);
-    furi_record_close("storage");
-    return result;
-}
-
-bool InfraredAppRemoteManager::load(string_t path) {
-    bool result = false;
-    Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
-    FlipperFormat* ff = flipper_format_file_alloc(storage);
-
-    FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path));
-    result = flipper_format_file_open_existing(ff, string_get_cstr(path));
-    if(result) {
-        string_t header;
-        string_init(header);
-        uint32_t version;
-        result = flipper_format_read_header(ff, header, &version);
-        if(result) {
-            result = !string_cmp_str(header, "IR signals file") && (version == 1);
-        }
-        string_clear(header);
-    }
-    if(result) {
-        string_t new_name;
-        string_init(new_name);
-
-        remote = std::make_unique<InfraredAppRemote>(path);
-        path_extract_filename(path, new_name, true);
-        remote->name = std::string(string_get_cstr(new_name));
-
-        string_clear(new_name);
-        InfraredAppSignal signal;
-        std::string signal_name;
-        while(infrared_parser_read_signal(ff, signal, signal_name)) {
-            remote->buttons.emplace_back(signal_name.c_str(), std::move(signal));
-        }
-    }
-
-    flipper_format_free(ff);
-    furi_record_close("storage");
-    return result;
-}

+ 0 - 189
applications/infrared/infrared_app_remote_manager.h

@@ -1,189 +0,0 @@
-/**
-  * @file infrared_app_remote_manager.h
-  * Infrared: Remote manager class.
-  * It holds remote, can load/save/rename remote,
-  * add/remove/rename buttons.
-  */
-#pragma once
-
-#include "infrared_app_signal.h"
-
-#include "m-string.h"
-#include <infrared_worker.h>
-#include <infrared.h>
-
-#include <cstdint>
-#include <string>
-#include <memory>
-#include <vector>
-
-/** Class to handle remote button */
-class InfraredAppRemoteButton {
-    /** Allow field access */
-    friend class InfraredAppRemoteManager;
-    /** Name of signal */
-    std::string name;
-    /** Signal data */
-    InfraredAppSignal signal;
-
-public:
-    /** Initialize remote button
-     *
-     * @param name - button name
-     * @param signal - signal to copy for remote button
-     */
-    InfraredAppRemoteButton(const char* name, const InfraredAppSignal& signal)
-        : name(name)
-        , signal(signal) {
-    }
-
-    /** Initialize remote button
-     *
-     * @param name - button name
-     * @param signal - signal to move for remote button
-     */
-    InfraredAppRemoteButton(const char* name, InfraredAppSignal&& signal)
-        : name(name)
-        , signal(std::move(signal)) {
-    }
-
-    /** Deinitialize remote button */
-    ~InfraredAppRemoteButton() {
-    }
-};
-
-/** Class to handle remote */
-class InfraredAppRemote {
-    /** Allow field access */
-    friend class InfraredAppRemoteManager;
-    /** Button container */
-    std::vector<InfraredAppRemoteButton> buttons;
-    /** Name of remote */
-    std::string name;
-    /** Path to remote file */
-    string_t path;
-
-public:
-    /** Initialize new remote
-     * 
-     * @param path - remote file path
-     */
-    InfraredAppRemote(string_t file_path) {
-        string_init_set(path, file_path);
-    }
-
-    ~InfraredAppRemote() {
-        string_clear(path);
-    }
-};
-
-/** Class to handle remote manager */
-class InfraredAppRemoteManager {
-    /** Remote instance. There can be 1 remote loaded at a time. */
-    std::unique_ptr<InfraredAppRemote> remote;
-
-public:
-    /** Restriction to button name length. Buttons larger are ignored. */
-    static constexpr const uint32_t max_button_name_length = 22;
-
-    /** Restriction to remote name length. Remotes larger are ignored. */
-    static constexpr const uint32_t max_remote_name_length = 22;
-
-    /** Construct button from signal, and create remote
-     *
-     * @param button_name - name of button to create
-     * @param signal - signal to create button from
-     * @retval true for success, false otherwise
-     * */
-    bool add_remote_with_button(const char* button_name, const InfraredAppSignal& signal);
-
-    /** Add button to current remote
-     *
-     * @param button_name - name of button to create
-     * @param signal - signal to create button from
-     * @retval true for success, false otherwise
-     * */
-    bool add_button(const char* button_name, const InfraredAppSignal& signal);
-
-    /** Rename button in current remote
-     *
-     * @param index - index of button to rename
-     * @param str - new button name
-     */
-    bool rename_button(uint32_t index, const char* str);
-
-    /** Rename current remote
-     *
-     * @param str - new remote name
-     */
-    bool rename_remote(const char* str);
-
-    /** Find vacant remote name. If suggested name is occupied,
-     * incremented digit(2,3,4,etc) added to name and check repeated.
-     *
-     * @param name - suggested remote name
-     * @param path - remote file path
-     */
-    void find_vacant_remote_name(string_t name, string_t path);
-
-    /** Get button list
-     *
-     * @retval container of button names
-     */
-    std::vector<std::string> get_button_list() const;
-
-    /** Get button name by index
-     *
-     * @param index - index of button to get name from
-     * @retval button name
-     */
-    std::string get_button_name(uint32_t index);
-
-    /** Get remote name
-     *
-     * @retval remote name
-     */
-    std::string get_remote_name();
-
-    /** Get number of buttons
-     *
-     * @retval number of buttons
-     */
-    size_t get_number_of_buttons();
-
-    /** Get button's signal
-     *
-     * @param index - index of interested button
-     * @retval signal
-     */
-    const InfraredAppSignal& get_button_data(size_t index) const;
-
-    /** Delete button
-     *
-     * @param index - index of interested button
-     * @retval true if success, false otherwise
-     */
-    bool delete_button(uint32_t index);
-
-    /** Delete remote
-     *
-     * @retval true if success, false otherwise
-     */
-    bool delete_remote();
-
-    /** Clean all loaded info in current remote */
-    void reset_remote();
-
-    /** Store current remote data on disk
-     *
-     * @retval true if success, false otherwise
-     */
-    bool store();
-
-    /** Load data from disk into current remote
-     *
-     * @param path - path to remote file
-     * @retval true if success, false otherwise
-     */
-    bool load(string_t path);
-};

+ 0 - 116
applications/infrared/infrared_app_signal.cpp

@@ -1,116 +0,0 @@
-#include "infrared_app_signal.h"
-#include <infrared_transmit.h>
-
-void InfraredAppSignal::copy_raw_signal(
-    const uint32_t* timings,
-    size_t size,
-    uint32_t frequency,
-    float duty_cycle) {
-    furi_assert(size);
-    furi_assert(timings);
-
-    payload.raw.frequency = frequency;
-    payload.raw.duty_cycle = duty_cycle;
-    payload.raw.timings_cnt = size;
-    if(size) {
-        payload.raw.timings = new uint32_t[size];
-        memcpy(payload.raw.timings, timings, size * sizeof(uint32_t));
-    }
-}
-
-void InfraredAppSignal::clear_timings() {
-    if(raw_signal) {
-        delete[] payload.raw.timings;
-        payload.raw.timings_cnt = 0;
-        payload.raw.timings = nullptr;
-    }
-}
-
-InfraredAppSignal::InfraredAppSignal(
-    const uint32_t* timings,
-    size_t timings_cnt,
-    uint32_t frequency,
-    float duty_cycle) {
-    raw_signal = true;
-    copy_raw_signal(timings, timings_cnt, frequency, duty_cycle);
-}
-
-InfraredAppSignal::InfraredAppSignal(const InfraredMessage* infrared_message) {
-    raw_signal = false;
-    payload.message = *infrared_message;
-}
-
-InfraredAppSignal& InfraredAppSignal::operator=(const InfraredAppSignal& other) {
-    clear_timings();
-    raw_signal = other.raw_signal;
-    if(!raw_signal) {
-        payload.message = other.payload.message;
-    } else {
-        copy_raw_signal(
-            other.payload.raw.timings,
-            other.payload.raw.timings_cnt,
-            other.payload.raw.frequency,
-            other.payload.raw.duty_cycle);
-    }
-
-    return *this;
-}
-
-InfraredAppSignal::InfraredAppSignal(const InfraredAppSignal& other) {
-    raw_signal = other.raw_signal;
-    if(!raw_signal) {
-        payload.message = other.payload.message;
-    } else {
-        copy_raw_signal(
-            other.payload.raw.timings,
-            other.payload.raw.timings_cnt,
-            other.payload.raw.frequency,
-            other.payload.raw.duty_cycle);
-    }
-}
-
-InfraredAppSignal::InfraredAppSignal(InfraredAppSignal&& other) {
-    raw_signal = other.raw_signal;
-    if(!raw_signal) {
-        payload.message = other.payload.message;
-    } else {
-        furi_assert(other.payload.raw.timings_cnt > 0);
-
-        payload.raw.timings = other.payload.raw.timings;
-        payload.raw.timings_cnt = other.payload.raw.timings_cnt;
-        payload.raw.frequency = other.payload.raw.frequency;
-        payload.raw.duty_cycle = other.payload.raw.duty_cycle;
-        other.payload.raw.timings = nullptr;
-        other.payload.raw.timings_cnt = 0;
-        other.raw_signal = false;
-    }
-}
-
-void InfraredAppSignal::set_message(const InfraredMessage* infrared_message) {
-    clear_timings();
-    raw_signal = false;
-    payload.message = *infrared_message;
-}
-
-void InfraredAppSignal::set_raw_signal(
-    uint32_t* timings,
-    size_t timings_cnt,
-    uint32_t frequency,
-    float duty_cycle) {
-    clear_timings();
-    raw_signal = true;
-    copy_raw_signal(timings, timings_cnt, frequency, duty_cycle);
-}
-
-void InfraredAppSignal::transmit() const {
-    if(!raw_signal) {
-        infrared_send(&payload.message, 1);
-    } else {
-        infrared_send_raw_ext(
-            payload.raw.timings,
-            payload.raw.timings_cnt,
-            true,
-            payload.raw.frequency,
-            payload.raw.duty_cycle);
-    }
-}

+ 0 - 134
applications/infrared/infrared_app_signal.h

@@ -1,134 +0,0 @@
-/**
-  * @file infrared_app_signal.h
-  * Infrared: Signal class
-  */
-#pragma once
-#include <infrared_worker.h>
-#include <stdint.h>
-#include <string>
-#include <infrared.h>
-
-/** Infrared application signal class */
-class InfraredAppSignal {
-public:
-    /** Raw signal structure */
-    typedef struct {
-        /** Timings amount */
-        size_t timings_cnt;
-        /** Samples of raw signal in ms */
-        uint32_t* timings;
-        /** PWM Frequency of raw signal */
-        uint32_t frequency;
-        /** PWM Duty cycle of raw signal */
-        float duty_cycle;
-    } RawSignal;
-
-private:
-    /** if true - signal is raw, if false - signal is parsed */
-    bool raw_signal;
-    /** signal data, either raw or parsed */
-    union {
-        /** signal data for parsed signal */
-        InfraredMessage message;
-        /** raw signal data */
-        RawSignal raw;
-    } payload;
-
-    /** Copy raw signal into object
-     *
-     * @param timings - timings (samples) of raw signal
-     * @param size - number of timings
-     * @frequency - PWM frequency of raw signal
-     * @duty_cycle - PWM duty cycle
-     */
-    void
-        copy_raw_signal(const uint32_t* timings, size_t size, uint32_t frequency, float duty_cycle);
-    /** Clear and free timings data */
-    void clear_timings();
-
-public:
-    /** Construct Infrared signal class */
-    InfraredAppSignal() {
-        raw_signal = false;
-        payload.message.protocol = InfraredProtocolUnknown;
-    }
-
-    /** Destruct signal class and free all allocated data */
-    ~InfraredAppSignal() {
-        clear_timings();
-    }
-
-    /** Construct object with raw signal
-     *
-     * @param timings - timings (samples) of raw signal
-     * @param size - number of timings
-     * @frequency - PWM frequency of raw signal
-     * @duty_cycle - PWM duty cycle
-     */
-    InfraredAppSignal(
-        const uint32_t* timings,
-        size_t timings_cnt,
-        uint32_t frequency,
-        float duty_cycle);
-
-    /** Construct object with parsed signal
-     *
-     * @param infrared_message - parsed_signal to construct from
-     */
-    InfraredAppSignal(const InfraredMessage* infrared_message);
-
-    /** Copy constructor */
-    InfraredAppSignal(const InfraredAppSignal& other);
-    /** Move constructor */
-    InfraredAppSignal(InfraredAppSignal&& other);
-
-    /** Assignment operator */
-    InfraredAppSignal& operator=(const InfraredAppSignal& signal);
-
-    /** Set object to parsed signal
-     *
-     * @param infrared_message - parsed_signal to construct from
-     */
-    void set_message(const InfraredMessage* infrared_message);
-
-    /** Set object to raw signal
-     *
-     * @param timings - timings (samples) of raw signal
-     * @param size - number of timings
-     * @frequency - PWM frequency of raw signal
-     * @duty_cycle - PWM duty cycle
-     */
-    void
-        set_raw_signal(uint32_t* timings, size_t timings_cnt, uint32_t frequency, float duty_cycle);
-
-    /** Transmit held signal (???) */
-    void transmit() const;
-
-    /** Show is held signal raw
-     *
-     * @retval true if signal is raw, false if signal is parsed
-     */
-    bool is_raw(void) const {
-        return raw_signal;
-    }
-
-    /** Get parsed signal.
-     * User must check is_raw() signal before calling this function.
-     *
-     * @retval parsed signal pointer
-     */
-    const InfraredMessage& get_message(void) const {
-        furi_assert(!raw_signal);
-        return payload.message;
-    }
-
-    /** Get raw signal.
-     * User must check is_raw() signal before calling this function.
-     *
-     * @retval raw signal
-     */
-    const RawSignal& get_raw_signal(void) const {
-        furi_assert(raw_signal);
-        return payload.raw;
-    }
-};

+ 0 - 163
applications/infrared/infrared_app_view_manager.cpp

@@ -1,163 +0,0 @@
-#include <gui/modules/button_menu.h>
-#include <gui/view_stack.h>
-#include <gui/modules/loading.h>
-#include <gui/modules/button_panel.h>
-#include <gui/modules/dialog_ex.h>
-#include <furi.h>
-#include <callback-connector.h>
-
-#include "infrared/infrared_app_view_manager.h"
-#include "infrared/view/infrared_progress_view.h"
-#include "infrared_app.h"
-#include "infrared/infrared_app_event.h"
-
-InfraredAppViewManager::InfraredAppViewManager() {
-    event_queue = osMessageQueueNew(10, sizeof(InfraredAppEvent), NULL);
-
-    view_dispatcher = view_dispatcher_alloc();
-    auto callback = cbc::obtain_connector(this, &InfraredAppViewManager::previous_view_callback);
-
-    gui = static_cast<Gui*>(furi_record_open("gui"));
-    view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
-
-    button_menu = button_menu_alloc();
-    submenu = submenu_alloc();
-    popup = popup_alloc();
-    dialog_ex = dialog_ex_alloc();
-    text_input = text_input_alloc();
-    button_panel = button_panel_alloc();
-    progress_view = infrared_progress_view_alloc();
-    loading_view = loading_alloc();
-    universal_view_stack = view_stack_alloc();
-    view_stack_add_view(universal_view_stack, button_panel_get_view(button_panel));
-    view_set_orientation(view_stack_get_view(universal_view_stack), ViewOrientationVertical);
-
-    add_view(ViewId::UniversalRemote, view_stack_get_view(universal_view_stack));
-    add_view(ViewId::ButtonMenu, button_menu_get_view(button_menu));
-    add_view(ViewId::Submenu, submenu_get_view(submenu));
-    add_view(ViewId::Popup, popup_get_view(popup));
-    add_view(ViewId::DialogEx, dialog_ex_get_view(dialog_ex));
-    add_view(ViewId::TextInput, text_input_get_view(text_input));
-
-    view_set_previous_callback(view_stack_get_view(universal_view_stack), callback);
-    view_set_previous_callback(button_menu_get_view(button_menu), callback);
-    view_set_previous_callback(submenu_get_view(submenu), callback);
-    view_set_previous_callback(popup_get_view(popup), callback);
-    view_set_previous_callback(dialog_ex_get_view(dialog_ex), callback);
-    view_set_previous_callback(text_input_get_view(text_input), callback);
-}
-
-InfraredAppViewManager::~InfraredAppViewManager() {
-    view_dispatcher_remove_view(
-        view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::UniversalRemote));
-    view_dispatcher_remove_view(
-        view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::ButtonMenu));
-    view_dispatcher_remove_view(
-        view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::TextInput));
-    view_dispatcher_remove_view(
-        view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::DialogEx));
-    view_dispatcher_remove_view(
-        view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::Submenu));
-    view_dispatcher_remove_view(
-        view_dispatcher, static_cast<uint32_t>(InfraredAppViewManager::ViewId::Popup));
-
-    view_stack_remove_view(universal_view_stack, button_panel_get_view(button_panel));
-    view_stack_free(universal_view_stack);
-    button_panel_free(button_panel);
-    submenu_free(submenu);
-    popup_free(popup);
-    button_menu_free(button_menu);
-    dialog_ex_free(dialog_ex);
-    text_input_free(text_input);
-    infrared_progress_view_free(progress_view);
-    loading_free(loading_view);
-
-    view_dispatcher_free(view_dispatcher);
-    furi_record_close("gui");
-    osMessageQueueDelete(event_queue);
-}
-
-void InfraredAppViewManager::switch_to(ViewId type) {
-    view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
-}
-
-TextInput* InfraredAppViewManager::get_text_input() {
-    return text_input;
-}
-
-DialogEx* InfraredAppViewManager::get_dialog_ex() {
-    return dialog_ex;
-}
-
-Submenu* InfraredAppViewManager::get_submenu() {
-    return submenu;
-}
-
-Popup* InfraredAppViewManager::get_popup() {
-    return popup;
-}
-
-ButtonMenu* InfraredAppViewManager::get_button_menu() {
-    return button_menu;
-}
-
-ButtonPanel* InfraredAppViewManager::get_button_panel() {
-    return button_panel;
-}
-
-InfraredProgressView* InfraredAppViewManager::get_progress() {
-    return progress_view;
-}
-
-Loading* InfraredAppViewManager::get_loading() {
-    return loading_view;
-}
-
-ViewStack* InfraredAppViewManager::get_universal_view_stack() {
-    return universal_view_stack;
-}
-
-osMessageQueueId_t InfraredAppViewManager::get_event_queue() {
-    return event_queue;
-}
-
-void InfraredAppViewManager::clear_events() {
-    InfraredAppEvent event;
-    while(osMessageQueueGet(event_queue, &event, NULL, 0) == osOK)
-        ;
-}
-
-void InfraredAppViewManager::receive_event(InfraredAppEvent* event) {
-    if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) {
-        event->type = InfraredAppEvent::Type::Tick;
-    }
-}
-
-void InfraredAppViewManager::send_event(InfraredAppEvent* event) {
-    uint32_t timeout = 0;
-    /* Rapid button hammering on signal send scenes causes queue overflow - ignore it,
-     * but try to keep button release event - it switches off INFRARED DMA sending. */
-    if(event->type == InfraredAppEvent::Type::MenuSelectedRelease) {
-        timeout = 200;
-    }
-    if((event->type == InfraredAppEvent::Type::DialogExSelected) &&
-       (event->payload.dialog_ex_result == DialogExReleaseCenter)) {
-        timeout = 200;
-    }
-
-    osMessageQueuePut(event_queue, event, 0, timeout);
-}
-
-uint32_t InfraredAppViewManager::previous_view_callback(void*) {
-    if(event_queue != NULL) {
-        InfraredAppEvent event;
-        event.type = InfraredAppEvent::Type::Back;
-        send_event(&event);
-    }
-
-    return VIEW_IGNORE;
-}
-
-void InfraredAppViewManager::add_view(ViewId view_type, View* view) {
-    view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
-}

+ 0 - 164
applications/infrared/infrared_app_view_manager.h

@@ -1,164 +0,0 @@
-/**
-  * @file infrared_app_view_manager.h
-  * Infrared: Scene events description
-  */
-#pragma once
-#include <gui/modules/button_menu.h>
-#include <gui/modules/text_input.h>
-#include <gui/view_stack.h>
-#include <gui/modules/button_panel.h>
-#include <furi.h>
-#include <gui/view_dispatcher.h>
-#include <gui/modules/dialog_ex.h>
-#include <gui/modules/submenu.h>
-#include <gui/modules/popup.h>
-#include <gui/modules/loading.h>
-
-#include "infrared_app_event.h"
-#include "view/infrared_progress_view.h"
-
-/** Infrared View manager class */
-class InfraredAppViewManager {
-public:
-    /** Infrared View Id enum, it is used
-     * to identify added views */
-    enum class ViewId : uint8_t {
-        DialogEx,
-        TextInput,
-        Submenu,
-        ButtonMenu,
-        UniversalRemote,
-        Popup,
-    };
-
-    /** Class constructor */
-    InfraredAppViewManager();
-    /** Class destructor */
-    ~InfraredAppViewManager();
-
-    /** Switch to another view
-     *
-     * @param id - view id to switch to
-     */
-    void switch_to(ViewId id);
-
-    /** Receive event from queue
-     *
-     * @param event - received event
-     */
-    void receive_event(InfraredAppEvent* event);
-
-    /** Send event to queue
-     *
-     * @param event - event to send
-     */
-    void send_event(InfraredAppEvent* event);
-
-    /** Clear events that already in queue
-     *
-     * @param event - event to send
-     */
-    void clear_events();
-
-    /** Get dialog_ex view module
-     *
-     * @retval dialog_ex view module
-     */
-    DialogEx* get_dialog_ex();
-
-    /** Get submenu view module
-     *
-     * @retval submenu view module
-     */
-    Submenu* get_submenu();
-
-    /** Get popup view module
-     *
-     * @retval popup view module
-     */
-    Popup* get_popup();
-
-    /** Get text_input view module
-     *
-     * @retval text_input view module
-     */
-    TextInput* get_text_input();
-
-    /** Get button_menu view module
-     *
-     * @retval button_menu view module
-     */
-    ButtonMenu* get_button_menu();
-
-    /** Get button_panel view module
-     *
-     * @retval button_panel view module
-     */
-    ButtonPanel* get_button_panel();
-
-    /** Get view_stack view module used in universal remote
-     *
-     * @retval view_stack view module
-     */
-    ViewStack* get_universal_view_stack();
-
-    /** Get progress view module
-     *
-     * @retval progress view module
-     */
-    InfraredProgressView* get_progress();
-
-    /** Get loading view module
-     *
-     * @retval loading view module
-     */
-    Loading* get_loading();
-
-    /** Get event queue
-     *
-     * @retval event queue
-     */
-    osMessageQueueId_t get_event_queue();
-
-    /** Callback to handle back button
-     *
-     * @param context - context to pass to callback
-     * @retval always returns VIEW_IGNORE
-     */
-    uint32_t previous_view_callback(void* context);
-
-private:
-    /** View Dispatcher instance.
-     * It handles view switching */
-    ViewDispatcher* view_dispatcher;
-    /** Gui instance */
-    Gui* gui;
-    /** Text input view module instance */
-    TextInput* text_input;
-    /** DialogEx view module instance */
-    DialogEx* dialog_ex;
-    /** Submenu view module instance */
-    Submenu* submenu;
-    /** Popup view module instance */
-    Popup* popup;
-    /** ButtonMenu view module instance */
-    ButtonMenu* button_menu;
-    /** ButtonPanel view module instance */
-    ButtonPanel* button_panel;
-    /** ViewStack view module instance */
-    ViewStack* universal_view_stack;
-    /** ProgressView view module instance */
-    InfraredProgressView* progress_view;
-    /** Loading view module instance */
-    Loading* loading_view;
-
-    /** Queue to handle events, which are processed in scenes */
-    osMessageQueueId_t event_queue;
-
-    /** Add View to pull of views
-     *
-     * @param view_id - id to identify view
-     * @param view - view to add
-     */
-    void add_view(ViewId view_id, View* view);
-};

+ 153 - 0
applications/infrared/infrared_brute_force.c

@@ -0,0 +1,153 @@
+#include "infrared_brute_force.h"
+
+#include <stdlib.h>
+#include <m-dict.h>
+#include <m-string.h>
+#include <flipper_format/flipper_format.h>
+
+#include "infrared_signal.h"
+
+typedef struct {
+    uint32_t index;
+    uint32_t count;
+} InfraredBruteForceRecord;
+
+DICT_DEF2(
+    InfraredBruteForceRecordDict,
+    string_t,
+    STRING_OPLIST,
+    InfraredBruteForceRecord,
+    M_POD_OPLIST);
+
+struct InfraredBruteForce {
+    FlipperFormat* ff;
+    const char* db_filename;
+    string_t current_record_name;
+    InfraredBruteForceRecordDict_t records;
+};
+
+InfraredBruteForce* infrared_brute_force_alloc() {
+    InfraredBruteForce* brute_force = malloc(sizeof(InfraredBruteForce));
+    brute_force->ff = NULL;
+    brute_force->db_filename = NULL;
+    string_init(brute_force->current_record_name);
+    InfraredBruteForceRecordDict_init(brute_force->records);
+    return brute_force;
+}
+
+void infrared_brute_force_free(InfraredBruteForce* brute_force) {
+    furi_assert(!brute_force->ff);
+    InfraredBruteForceRecordDict_clear(brute_force->records);
+    string_clear(brute_force->current_record_name);
+    free(brute_force);
+}
+
+void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
+    brute_force->db_filename = db_filename;
+}
+
+bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
+    furi_assert(brute_force->db_filename);
+    bool success = false;
+
+    Storage* storage = furi_record_open("storage");
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    success = flipper_format_file_open_existing(ff, brute_force->db_filename);
+    if(success) {
+        string_t signal_name;
+        string_init(signal_name);
+        while(flipper_format_read_string(ff, "name", signal_name)) {
+            InfraredBruteForceRecord* record =
+                InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
+            if(record) {
+                ++(record->count);
+            }
+        }
+        string_clear(signal_name);
+    }
+
+    flipper_format_free(ff);
+    furi_record_close("storage");
+    return success;
+}
+
+bool infrared_brute_force_start(
+    InfraredBruteForce* brute_force,
+    uint32_t index,
+    uint32_t* record_count) {
+    bool success = false;
+    *record_count = 0;
+
+    InfraredBruteForceRecordDict_it_t it;
+    for(InfraredBruteForceRecordDict_it(it, brute_force->records);
+        !InfraredBruteForceRecordDict_end_p(it);
+        InfraredBruteForceRecordDict_next(it)) {
+        const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
+        if(record->value.index == index) {
+            *record_count = record->value.count;
+            if(*record_count) {
+                string_set(brute_force->current_record_name, record->key);
+            }
+            break;
+        }
+    }
+
+    if(*record_count) {
+        Storage* storage = furi_record_open("storage");
+        brute_force->ff = flipper_format_file_alloc(storage);
+        success = flipper_format_file_open_existing(brute_force->ff, brute_force->db_filename);
+        if(!success) {
+            flipper_format_free(brute_force->ff);
+            furi_record_close("storage");
+        }
+    }
+    return success;
+}
+
+bool infrared_brute_force_is_started(InfraredBruteForce* brute_force) {
+    return brute_force->ff;
+}
+
+void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
+    furi_assert(string_size(brute_force->current_record_name));
+    furi_assert(brute_force->ff);
+
+    string_reset(brute_force->current_record_name);
+    flipper_format_free(brute_force->ff);
+    furi_record_close("storage");
+    brute_force->ff = NULL;
+}
+
+bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
+    furi_assert(string_size(brute_force->current_record_name));
+    furi_assert(brute_force->ff);
+    bool success = false;
+
+    string_t signal_name;
+    string_init(signal_name);
+    InfraredSignal* signal = infrared_signal_alloc();
+
+    do {
+        success = infrared_signal_read(signal, brute_force->ff, signal_name);
+    } while(success && !string_equal_p(brute_force->current_record_name, signal_name));
+
+    if(success) {
+        infrared_signal_transmit(signal);
+    }
+
+    infrared_signal_free(signal);
+    string_clear(signal_name);
+    return success;
+}
+
+void infrared_brute_force_add_record(
+    InfraredBruteForce* brute_force,
+    uint32_t index,
+    const char* name) {
+    InfraredBruteForceRecord value = {.index = index, .count = 0};
+    string_t key;
+    string_init_set_str(key, name);
+    InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
+    string_clear(key);
+}

+ 22 - 0
applications/infrared/infrared_brute_force.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct InfraredBruteForce InfraredBruteForce;
+
+InfraredBruteForce* infrared_brute_force_alloc();
+void infrared_brute_force_free(InfraredBruteForce* brute_force);
+void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename);
+bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force);
+bool infrared_brute_force_start(
+    InfraredBruteForce* brute_force,
+    uint32_t index,
+    uint32_t* record_count);
+bool infrared_brute_force_is_started(InfraredBruteForce* brute_force);
+void infrared_brute_force_stop(InfraredBruteForce* brute_force);
+bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
+void infrared_brute_force_add_record(
+    InfraredBruteForce* brute_force,
+    uint32_t index,
+    const char* name);

+ 52 - 53
applications/infrared/cli/infrared_cli.cpp → applications/infrared/infrared_cli.c

@@ -1,16 +1,12 @@
-#include <furi_hal_delay.h>
-#include <infrared.h>
+#include <m-string.h>
 #include <cli/cli.h>
 #include <cli/cli.h>
-#include <cmsis_os2.h>
+#include <infrared.h>
 #include <infrared_worker.h>
 #include <infrared_worker.h>
-#include <furi.h>
 #include <furi_hal_infrared.h>
 #include <furi_hal_infrared.h>
-#include <sstream>
-#include <string>
-#include <m-string.h>
-#include <infrared_transmit.h>
-#include <sys/types.h>
-#include "../helpers/infrared_parser.h"
+
+#include "infrared_signal.h"
+
+#define INFRARED_CLI_BUF_SIZE 10
 
 
 static void infrared_cli_start_ir_rx(Cli* cli, string_t args);
 static void infrared_cli_start_ir_rx(Cli* cli, string_t args);
 static void infrared_cli_start_ir_tx(Cli* cli, string_t args);
 static void infrared_cli_start_ir_tx(Cli* cli, string_t args);
@@ -92,79 +88,83 @@ static void infrared_cli_print_usage(void) {
         INFRARED_MAX_FREQUENCY);
         INFRARED_MAX_FREQUENCY);
 }
 }
 
 
-static bool parse_message(const char* str, InfraredMessage* message) {
+static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) {
     char protocol_name[32];
     char protocol_name[32];
-    int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message->address, &message->command);
+    InfraredMessage message;
+    int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message.address, &message.command);
 
 
     if(parsed != 3) {
     if(parsed != 3) {
         return false;
         return false;
     }
     }
 
 
-    message->protocol = infrared_get_protocol_by_name(protocol_name);
-    message->repeat = false;
-
-    return infrared_parser_is_parsed_signal_valid(message);
+    message.repeat = false;
+    infrared_signal_set_message(signal, &message);
+    return infrared_signal_is_valid(signal);
 }
 }
 
 
-static bool parse_signal_raw(
-    const char* str,
-    uint32_t* timings,
-    uint32_t* timings_cnt,
-    float* duty_cycle,
-    uint32_t* frequency) {
-    char frequency_str[10];
-    char duty_cycle_str[10];
+static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
+    char frequency_str[INFRARED_CLI_BUF_SIZE];
+    char duty_cycle_str[INFRARED_CLI_BUF_SIZE];
     int parsed = sscanf(str, "RAW F:%9s DC:%9s", frequency_str, duty_cycle_str);
     int parsed = sscanf(str, "RAW F:%9s DC:%9s", frequency_str, duty_cycle_str);
-    if(parsed != 2) return false;
 
 
-    *frequency = atoi(frequency_str);
-    *duty_cycle = (float)atoi(duty_cycle_str) / 100;
-    str += strlen(frequency_str) + strlen(duty_cycle_str) + 10;
+    if(parsed != 2) {
+        return false;
+    }
+
+    uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT);
+    uint32_t frequency = atoi(frequency_str);
+    float duty_cycle = (float)atoi(duty_cycle_str) / 100;
 
 
-    uint32_t timings_cnt_max = *timings_cnt;
-    *timings_cnt = 0;
+    str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE;
 
 
+    size_t timings_size = 0;
     while(1) {
     while(1) {
-        char timing_str[10];
-        for(; *str == ' '; ++str)
-            ;
-        if(1 != sscanf(str, "%9s", timing_str)) break;
+        while(*str == ' ') {
+            ++str;
+        }
+
+        char timing_str[INFRARED_CLI_BUF_SIZE];
+        if(sscanf(str, "%9s", timing_str) != 1) {
+            break;
+        }
+
         str += strlen(timing_str);
         str += strlen(timing_str);
         uint32_t timing = atoi(timing_str);
         uint32_t timing = atoi(timing_str);
-        if(timing <= 0) break;
-        if(*timings_cnt >= timings_cnt_max) break;
-        timings[*timings_cnt] = timing;
-        ++*timings_cnt;
+
+        if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) {
+            break;
+        }
+
+        timings[timings_size] = timing;
+        ++timings_size;
     }
     }
 
 
-    return infrared_parser_is_raw_signal_valid(*frequency, *duty_cycle, *timings_cnt);
+    infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
+    free(timings);
+
+    return infrared_signal_is_valid(signal);
 }
 }
 
 
 static void infrared_cli_start_ir_tx(Cli* cli, string_t args) {
 static void infrared_cli_start_ir_tx(Cli* cli, string_t args) {
     UNUSED(cli);
     UNUSED(cli);
-    InfraredMessage message;
     const char* str = string_get_cstr(args);
     const char* str = string_get_cstr(args);
-    uint32_t frequency;
-    float duty_cycle;
-    uint32_t timings_cnt = MAX_TIMINGS_AMOUNT;
-    uint32_t* timings = (uint32_t*)malloc(sizeof(uint32_t) * timings_cnt);
-
-    if(parse_message(str, &message)) {
-        infrared_send(&message, 1);
-    } else if(parse_signal_raw(str, timings, &timings_cnt, &duty_cycle, &frequency)) {
-        infrared_send_raw_ext(timings, timings_cnt, true, frequency, duty_cycle);
+    InfraredSignal* signal = infrared_signal_alloc();
+
+    bool success = infrared_cli_parse_message(str, signal) || infrared_cli_parse_raw(str, signal);
+    if(success) {
+        infrared_signal_transmit(signal);
     } else {
     } else {
         printf("Wrong arguments.\r\n");
         printf("Wrong arguments.\r\n");
         infrared_cli_print_usage();
         infrared_cli_print_usage();
     }
     }
 
 
-    free(timings);
+    infrared_signal_free(signal);
 }
 }
 
 
 static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) {
 static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) {
     UNUSED(context);
     UNUSED(context);
     if(furi_hal_infrared_is_busy()) {
     if(furi_hal_infrared_is_busy()) {
-        printf("INFRARED is busy. Exit.");
+        printf("INFRARED is busy. Exiting.");
         return;
         return;
     }
     }
 
 
@@ -189,8 +189,7 @@ static void infrared_cli_start_ir(Cli* cli, string_t args, void* context) {
         infrared_cli_print_usage();
         infrared_cli_print_usage();
     }
     }
 }
 }
-
-extern "C" void infrared_on_system_start() {
+void infrared_on_system_start() {
 #ifdef SRV_CLI
 #ifdef SRV_CLI
     Cli* cli = (Cli*)furi_record_open("cli");
     Cli* cli = (Cli*)furi_record_open("cli");
     cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);
     cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);

+ 51 - 0
applications/infrared/infrared_custom_event.h

@@ -0,0 +1,51 @@
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+
+enum InfraredCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    InfraredCustomEventTypeReserved = 100,
+    InfraredCustomEventTypeMenuSelected,
+    InfraredCustomEventTypeTransmitStarted,
+    InfraredCustomEventTypeTransmitStopped,
+    InfraredCustomEventTypeSignalReceived,
+    InfraredCustomEventTypeTextEditDone,
+    InfraredCustomEventTypePopupTimeout,
+    InfraredCustomEventTypeButtonSelected,
+    InfraredCustomEventTypeBackPressed,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} InfraredCustomEvent;
+#pragma pack(pop)
+
+static inline uint32_t infrared_custom_event_pack(uint16_t type, int16_t value) {
+    InfraredCustomEvent event = {.content = {.type = type, .value = value}};
+    return event.packed_value;
+}
+
+static inline void
+    infrared_custom_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
+    InfraredCustomEvent event = {.packed_value = packed_value};
+    if(type) *type = event.content.type;
+    if(value) *value = event.content.value;
+}
+
+static inline uint16_t infrared_custom_event_get_type(uint32_t packed_value) {
+    uint16_t type;
+    infrared_custom_event_unpack(packed_value, &type, NULL);
+    return type;
+}
+
+static inline int16_t infrared_custom_event_get_value(uint32_t packed_value) {
+    int16_t value;
+    infrared_custom_event_unpack(packed_value, NULL, &value);
+    return value;
+}

+ 132 - 0
applications/infrared/infrared_i.h

@@ -0,0 +1,132 @@
+#pragma once
+
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_stack.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/button_menu.h>
+#include <gui/modules/button_panel.h>
+
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+
+#include <notification/notification_messages.h>
+
+#include <infrared_worker.h>
+
+#include "infrared.h"
+#include "infrared_remote.h"
+#include "infrared_brute_force.h"
+#include "infrared_custom_event.h"
+
+#include "scenes/infrared_scene.h"
+#include "views/infrared_progress_view.h"
+#include "views/infrared_debug_view.h"
+
+#define INFRARED_FILE_NAME_SIZE 100
+#define INFRARED_TEXT_STORE_NUM 2
+#define INFRARED_TEXT_STORE_SIZE 128
+
+#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
+#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
+
+#define INFRARED_APP_FOLDER "/any/infrared"
+#define INFRARED_APP_EXTENSION ".ir"
+
+#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
+
+typedef enum {
+    InfraredButtonIndexNone = -1,
+} InfraredButtonIndex;
+
+typedef enum {
+    InfraredEditTargetNone,
+    InfraredEditTargetRemote,
+    InfraredEditTargetButton,
+} InfraredEditTarget;
+
+typedef enum {
+    InfraredEditModeNone,
+    InfraredEditModeRename,
+    InfraredEditModeDelete,
+} InfraredEditMode;
+
+typedef struct {
+    bool is_learning_new_remote;
+    bool is_debug_enabled;
+    InfraredEditTarget edit_target : 8;
+    InfraredEditMode edit_mode : 8;
+    int32_t current_button_index;
+} InfraredAppState;
+
+struct Infrared {
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+
+    Gui* gui;
+    Storage* storage;
+    DialogsApp* dialogs;
+    NotificationApp* notifications;
+    InfraredWorker* worker;
+    InfraredRemote* remote;
+    InfraredSignal* received_signal;
+    InfraredBruteForce* brute_force;
+
+    Submenu* submenu;
+    TextInput* text_input;
+    DialogEx* dialog_ex;
+    ButtonMenu* button_menu;
+    Popup* popup;
+
+    ViewStack* view_stack;
+    InfraredDebugView* debug_view;
+
+    ButtonPanel* button_panel;
+    Loading* loading;
+    InfraredProgressView* progress;
+
+    string_t file_path;
+    char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
+    InfraredAppState app_state;
+};
+
+typedef enum {
+    InfraredViewSubmenu,
+    InfraredViewTextInput,
+    InfraredViewDialogEx,
+    InfraredViewButtonMenu,
+    InfraredViewPopup,
+    InfraredViewStack,
+    InfraredViewDebugView,
+} InfraredView;
+
+typedef enum {
+    InfraredNotificationMessageSuccess,
+    InfraredNotificationMessageGreenOn,
+    InfraredNotificationMessageGreenOff,
+    InfraredNotificationMessageBlinkRead,
+    InfraredNotificationMessageBlinkSend,
+} InfraredNotificationMessage;
+
+bool infrared_add_remote_with_button(Infrared* infrared, const char* name, InfraredSignal* signal);
+bool infrared_rename_current_remote(Infrared* infrared, const char* name);
+void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal);
+void infrared_tx_start_button_index(Infrared* infrared, size_t button_index);
+void infrared_tx_start_received(Infrared* infrared);
+void infrared_tx_stop(Infrared* infrared);
+void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...);
+void infrared_text_store_clear(Infrared* infrared, uint32_t bank);
+void infrared_play_notification_message(Infrared* infrared, uint32_t message);
+void infrared_show_loading_popup(Infrared* infrared, bool show);
+
+void infrared_signal_sent_callback(void* context);
+void infrared_signal_received_callback(void* context, InfraredWorkerSignal* received_signal);
+void infrared_text_input_callback(void* context);
+void infrared_popup_timeout_callback(void* context);

+ 176 - 0
applications/infrared/infrared_remote.c

@@ -0,0 +1,176 @@
+#include "infrared_remote.h"
+
+#include <stdlib.h>
+#include <m-string.h>
+#include <m-array.h>
+#include <toolbox/path.h>
+#include <storage/storage.h>
+#include <furi/common_defines.h>
+
+#define TAG "InfraredRemote"
+
+ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST);
+
+struct InfraredRemote {
+    InfraredButtonArray_t buttons;
+    string_t name;
+    string_t path;
+};
+
+static void infrared_remote_clear_buttons(InfraredRemote* remote) {
+    InfraredButtonArray_it_t it;
+    for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
+        InfraredButtonArray_next(it)) {
+        infrared_remote_button_free(*InfraredButtonArray_cref(it));
+    }
+    InfraredButtonArray_reset(remote->buttons);
+}
+
+InfraredRemote* infrared_remote_alloc() {
+    InfraredRemote* remote = malloc(sizeof(InfraredRemote));
+    InfraredButtonArray_init(remote->buttons);
+    string_init(remote->name);
+    string_init(remote->path);
+    return remote;
+}
+
+void infrared_remote_free(InfraredRemote* remote) {
+    infrared_remote_clear_buttons(remote);
+    InfraredButtonArray_clear(remote->buttons);
+    string_clear(remote->path);
+    string_clear(remote->name);
+    free(remote);
+}
+
+void infrared_remote_reset(InfraredRemote* remote) {
+    infrared_remote_clear_buttons(remote);
+    string_reset(remote->name);
+    string_reset(remote->path);
+}
+
+void infrared_remote_set_name(InfraredRemote* remote, const char* name) {
+    string_set_str(remote->name, name);
+}
+
+const char* infrared_remote_get_name(InfraredRemote* remote) {
+    return string_get_cstr(remote->name);
+}
+
+void infrared_remote_set_path(InfraredRemote* remote, const char* path) {
+    string_set_str(remote->path, path);
+}
+
+const char* infrared_remote_get_path(InfraredRemote* remote) {
+    return string_get_cstr(remote->path);
+}
+
+size_t infrared_remote_get_button_count(InfraredRemote* remote) {
+    return InfraredButtonArray_size(remote->buttons);
+}
+
+InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index) {
+    furi_assert(index < InfraredButtonArray_size(remote->buttons));
+    return *InfraredButtonArray_get(remote->buttons, index);
+}
+
+bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
+    InfraredRemoteButton* button = infrared_remote_button_alloc();
+    infrared_remote_button_set_name(button, name);
+    infrared_remote_button_set_signal(button, signal);
+    InfraredButtonArray_push_back(remote->buttons, button);
+    return infrared_remote_store(remote);
+}
+
+bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index) {
+    furi_assert(index < InfraredButtonArray_size(remote->buttons));
+    InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, index);
+    infrared_remote_button_set_name(button, new_name);
+    return infrared_remote_store(remote);
+}
+
+bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) {
+    furi_assert(index < InfraredButtonArray_size(remote->buttons));
+    InfraredRemoteButton* button;
+    InfraredButtonArray_pop_at(&button, remote->buttons, index);
+    infrared_remote_button_free(button);
+    return infrared_remote_store(remote);
+}
+
+bool infrared_remote_store(InfraredRemote* remote) {
+    Storage* storage = furi_record_open("storage");
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    const char* path = string_get_cstr(remote->path);
+
+    FURI_LOG_I(TAG, "store file: \'%s\'", path);
+
+    bool success = flipper_format_file_open_always(ff, path) &&
+                   flipper_format_write_header_cstr(ff, "IR signals file", 1);
+    if(success) {
+        InfraredButtonArray_it_t it;
+        for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
+            InfraredButtonArray_next(it)) {
+            InfraredRemoteButton* button = *InfraredButtonArray_cref(it);
+            success = infrared_signal_save(
+                infrared_remote_button_get_signal(button),
+                ff,
+                infrared_remote_button_get_name(button));
+            if(!success) {
+                break;
+            }
+        }
+    }
+
+    flipper_format_free(ff);
+    furi_record_close("storage");
+    return success;
+}
+
+bool infrared_remote_load(InfraredRemote* remote, string_t path) {
+    Storage* storage = furi_record_open("storage");
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    string_t buf;
+    string_init(buf);
+
+    FURI_LOG_I(TAG, "load file: \'%s\'", string_get_cstr(path));
+    bool success = flipper_format_file_open_existing(ff, string_get_cstr(path));
+
+    if(success) {
+        uint32_t version;
+        success = flipper_format_read_header(ff, buf, &version) &&
+                  !string_cmp_str(buf, "IR signals file") && (version == 1);
+    }
+
+    if(success) {
+        path_extract_filename(path, buf, true);
+        infrared_remote_clear_buttons(remote);
+        infrared_remote_set_name(remote, string_get_cstr(buf));
+        infrared_remote_set_path(remote, string_get_cstr(path));
+
+        for(bool can_read = true; can_read;) {
+            InfraredRemoteButton* button = infrared_remote_button_alloc();
+            can_read = infrared_signal_read(infrared_remote_button_get_signal(button), ff, buf);
+            if(can_read) {
+                infrared_remote_button_set_name(button, string_get_cstr(buf));
+                InfraredButtonArray_push_back(remote->buttons, button);
+            } else {
+                infrared_remote_button_free(button);
+            }
+        }
+    }
+
+    string_clear(buf);
+    flipper_format_free(ff);
+    furi_record_close("storage");
+    return success;
+}
+
+bool infrared_remote_remove(InfraredRemote* remote) {
+    Storage* storage = furi_record_open("storage");
+
+    FS_Error status = storage_common_remove(storage, string_get_cstr(remote->path));
+    infrared_remote_reset(remote);
+
+    furi_record_close("storage");
+    return (status == FSE_OK || status == FSE_NOT_EXIST);
+}

+ 28 - 0
applications/infrared/infrared_remote.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "infrared_remote_button.h"
+
+typedef struct InfraredRemote InfraredRemote;
+
+InfraredRemote* infrared_remote_alloc();
+void infrared_remote_free(InfraredRemote* remote);
+void infrared_remote_reset(InfraredRemote* remote);
+
+void infrared_remote_set_name(InfraredRemote* remote, const char* name);
+const char* infrared_remote_get_name(InfraredRemote* remote);
+
+void infrared_remote_set_path(InfraredRemote* remote, const char* path);
+const char* infrared_remote_get_path(InfraredRemote* remote);
+
+size_t infrared_remote_get_button_count(InfraredRemote* remote);
+InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
+
+bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
+bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
+bool infrared_remote_delete_button(InfraredRemote* remote, size_t index);
+
+bool infrared_remote_store(InfraredRemote* remote);
+bool infrared_remote_load(InfraredRemote* remote, string_t path);
+bool infrared_remote_remove(InfraredRemote* remote);

+ 38 - 0
applications/infrared/infrared_remote_button.c

@@ -0,0 +1,38 @@
+#include "infrared_remote_button.h"
+
+#include <stdlib.h>
+#include <m-string.h>
+
+struct InfraredRemoteButton {
+    string_t name;
+    InfraredSignal* signal;
+};
+
+InfraredRemoteButton* infrared_remote_button_alloc() {
+    InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton));
+    string_init(button->name);
+    button->signal = infrared_signal_alloc();
+    return button;
+}
+
+void infrared_remote_button_free(InfraredRemoteButton* button) {
+    string_clear(button->name);
+    infrared_signal_free(button->signal);
+    free(button);
+}
+
+void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name) {
+    string_set_str(button->name, name);
+}
+
+const char* infrared_remote_button_get_name(InfraredRemoteButton* button) {
+    return string_get_cstr(button->name);
+}
+
+void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) {
+    infrared_signal_set_signal(button->signal, signal);
+}
+
+InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button) {
+    return button->signal;
+}

+ 14 - 0
applications/infrared/infrared_remote_button.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include "infrared_signal.h"
+
+typedef struct InfraredRemoteButton InfraredRemoteButton;
+
+InfraredRemoteButton* infrared_remote_button_alloc();
+void infrared_remote_button_free(InfraredRemoteButton* button);
+
+void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name);
+const char* infrared_remote_button_get_name(InfraredRemoteButton* button);
+
+void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal);
+InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button);

+ 0 - 9
applications/infrared/infrared_runner.cpp

@@ -1,9 +0,0 @@
-#include "infrared_app.h"
-
-extern "C" int32_t infrared_app(void* p) {
-    InfraredApp* app = new InfraredApp();
-    int32_t result = app->run(p);
-    delete app;
-
-    return result;
-}

+ 264 - 0
applications/infrared/infrared_signal.c

@@ -0,0 +1,264 @@
+#include "infrared_signal.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <furi/check.h>
+#include <infrared_worker.h>
+#include <infrared_transmit.h>
+
+#define TAG "InfraredSignal"
+
+struct InfraredSignal {
+    bool is_raw;
+    union {
+        InfraredMessage message;
+        InfraredRawSignal raw;
+    } payload;
+};
+
+static void infrared_signal_clear_timings(InfraredSignal* signal) {
+    if(signal->is_raw) {
+        free(signal->payload.raw.timings);
+        signal->payload.raw.timings_size = 0;
+        signal->payload.raw.timings = NULL;
+    }
+}
+
+static bool infrared_signal_is_message_valid(InfraredMessage* message) {
+    if(!infrared_is_protocol_valid(message->protocol)) {
+        FURI_LOG_E(TAG, "Unknown protocol");
+        return false;
+    }
+
+    uint32_t address_length = infrared_get_protocol_address_length(message->protocol);
+    uint32_t address_mask = (1UL << address_length) - 1;
+
+    if(message->address != (message->address & address_mask)) {
+        FURI_LOG_E(
+            TAG,
+            "Address is out of range (mask 0x%08lX): 0x%lX\r\n",
+            address_mask,
+            message->address);
+        return false;
+    }
+
+    uint32_t command_length = infrared_get_protocol_command_length(message->protocol);
+    uint32_t command_mask = (1UL << command_length) - 1;
+
+    if(message->command != (message->command & command_mask)) {
+        FURI_LOG_E(
+            TAG,
+            "Command is out of range (mask 0x%08lX): 0x%lX\r\n",
+            command_mask,
+            message->command);
+        return false;
+    }
+
+    return true;
+}
+
+static bool infrared_signal_is_raw_valid(InfraredRawSignal* raw) {
+    if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) {
+        FURI_LOG_E(
+            TAG,
+            "Frequency is out of range (%lX - %lX): %lX",
+            INFRARED_MIN_FREQUENCY,
+            INFRARED_MAX_FREQUENCY,
+            raw->frequency);
+        return false;
+
+    } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) {
+        FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle);
+        return false;
+
+    } else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) {
+        FURI_LOG_E(
+            TAG,
+            "Timings amount is out of range (0 - %lX): %lX",
+            MAX_TIMINGS_AMOUNT,
+            raw->timings_size);
+        return false;
+    }
+
+    return true;
+}
+
+static inline bool infrared_signal_save_message(InfraredMessage* message, FlipperFormat* ff) {
+    const char* protocol_name = infrared_get_protocol_name(message->protocol);
+    return flipper_format_write_string_cstr(ff, "type", "parsed") &&
+           flipper_format_write_string_cstr(ff, "protocol", protocol_name) &&
+           flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) &&
+           flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4);
+}
+
+static inline bool infrared_signal_save_raw(InfraredRawSignal* raw, FlipperFormat* ff) {
+    furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT);
+    return flipper_format_write_string_cstr(ff, "type", "raw") &&
+           flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) &&
+           flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) &&
+           flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size);
+}
+
+static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) {
+    string_t buf;
+    string_init(buf);
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "protocol", buf)) break;
+
+        InfraredMessage message;
+        message.protocol = infrared_get_protocol_by_name(string_get_cstr(buf));
+
+        success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) &&
+                  flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) &&
+                  infrared_signal_is_message_valid(&message);
+
+        if(!success) break;
+
+        infrared_signal_set_message(signal, &message);
+    } while(0);
+
+    string_clear(buf);
+    return success;
+}
+
+static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) {
+    uint32_t timings_size, frequency;
+    float duty_cycle;
+
+    bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) &&
+                   flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) &&
+                   flipper_format_get_value_count(ff, "data", &timings_size);
+
+    if(!success || timings_size > MAX_TIMINGS_AMOUNT) {
+        return false;
+    }
+
+    uint32_t* timings = malloc(sizeof(uint32_t) * timings_size);
+    success = flipper_format_read_uint32(ff, "data", timings, timings_size);
+
+    if(success) {
+        infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
+    }
+
+    free(timings);
+    return success;
+}
+
+InfraredSignal* infrared_signal_alloc() {
+    InfraredSignal* signal = malloc(sizeof(InfraredSignal));
+
+    signal->is_raw = false;
+    signal->payload.message.protocol = InfraredProtocolUnknown;
+
+    return signal;
+}
+
+void infrared_signal_free(InfraredSignal* signal) {
+    infrared_signal_clear_timings(signal);
+    free(signal);
+}
+
+bool infrared_signal_is_raw(InfraredSignal* signal) {
+    return signal->is_raw;
+}
+
+bool infrared_signal_is_valid(InfraredSignal* signal) {
+    return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) :
+                            infrared_signal_is_message_valid(&signal->payload.message);
+}
+
+void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) {
+    if(other->is_raw) {
+        const InfraredRawSignal* raw = &other->payload.raw;
+        infrared_signal_set_raw_signal(
+            signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
+    } else {
+        const InfraredMessage* message = &other->payload.message;
+        infrared_signal_set_message(signal, message);
+    }
+}
+
+void infrared_signal_set_raw_signal(
+    InfraredSignal* signal,
+    const uint32_t* timings,
+    size_t timings_size,
+    uint32_t frequency,
+    float duty_cycle) {
+    infrared_signal_clear_timings(signal);
+
+    signal->is_raw = true;
+
+    signal->payload.raw.timings_size = timings_size;
+    signal->payload.raw.frequency = frequency;
+    signal->payload.raw.duty_cycle = duty_cycle;
+
+    signal->payload.raw.timings = malloc(timings_size * sizeof(uint32_t));
+    memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t));
+}
+
+InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal) {
+    furi_assert(signal->is_raw);
+    return &signal->payload.raw;
+}
+
+void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) {
+    infrared_signal_clear_timings(signal);
+
+    signal->is_raw = false;
+    signal->payload.message = *message;
+}
+
+InfraredMessage* infrared_signal_get_message(InfraredSignal* signal) {
+    furi_assert(!signal->is_raw);
+    return &signal->payload.message;
+}
+
+bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
+    if(!flipper_format_write_comment_cstr(ff, "") ||
+       !flipper_format_write_string_cstr(ff, "name", name)) {
+        return false;
+    } else if(signal->is_raw) {
+        return infrared_signal_save_raw(&signal->payload.raw, ff);
+    } else {
+        return infrared_signal_save_message(&signal->payload.message, ff);
+    }
+}
+
+bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name) {
+    string_t buf;
+    string_init(buf);
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "name", buf)) break;
+        string_set(name, buf);
+        if(!flipper_format_read_string(ff, "type", buf)) break;
+        if(!string_cmp_str(buf, "raw")) {
+            success = infrared_signal_read_raw(signal, ff);
+        } else if(!string_cmp_str(buf, "parsed")) {
+            success = infrared_signal_read_message(signal, ff);
+        } else {
+            FURI_LOG_E(TAG, "Unknown type of signal (allowed - raw/parsed) ");
+        }
+    } while(0);
+
+    string_clear(buf);
+    return success;
+}
+
+void infrared_signal_transmit(InfraredSignal* signal) {
+    if(signal->is_raw) {
+        InfraredRawSignal* raw_signal = &signal->payload.raw;
+        infrared_send_raw_ext(
+            raw_signal->timings,
+            raw_signal->timings_size,
+            true,
+            raw_signal->frequency,
+            raw_signal->duty_cycle);
+    } else {
+        InfraredMessage* message = &signal->payload.message;
+        infrared_send(message, 1);
+    }
+}

+ 41 - 0
applications/infrared/infrared_signal.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <infrared.h>
+#include <flipper_format/flipper_format.h>
+
+typedef struct InfraredSignal InfraredSignal;
+
+typedef struct {
+    size_t timings_size;
+    uint32_t* timings;
+    uint32_t frequency;
+    float duty_cycle;
+} InfraredRawSignal;
+
+InfraredSignal* infrared_signal_alloc();
+void infrared_signal_free(InfraredSignal* signal);
+
+bool infrared_signal_is_raw(InfraredSignal* signal);
+bool infrared_signal_is_valid(InfraredSignal* signal);
+
+void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other);
+
+void infrared_signal_set_raw_signal(
+    InfraredSignal* signal,
+    const uint32_t* timings,
+    size_t timings_size,
+    uint32_t frequency,
+    float duty_cycle);
+InfraredRawSignal* infrared_signal_get_raw_signal(InfraredSignal* signal);
+
+void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message);
+InfraredMessage* infrared_signal_get_message(InfraredSignal* signal);
+
+bool infrared_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
+bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, string_t name);
+
+void infrared_signal_transmit(InfraredSignal* signal);

+ 0 - 305
applications/infrared/scene/infrared_app_scene.h

@@ -1,305 +0,0 @@
-/**
- * @file infrared_app_scene.h
- * Infrared: Application scenes
- */
-#pragma once
-#include "../infrared_app_event.h"
-#include <furi_hal_infrared.h>
-#include "infrared.h"
-#include <vector>
-#include <string>
-#include "../infrared_app_brute_force.h"
-
-/** Anonymous class */
-class InfraredApp;
-
-/** Base Scene class */
-class InfraredAppScene {
-public:
-    /** Called when enter scene */
-    virtual void on_enter(InfraredApp* app) = 0;
-    /** Events handler callback */
-    virtual bool on_event(InfraredApp* app, InfraredAppEvent* event) = 0;
-    /** Called when exit scene */
-    virtual void on_exit(InfraredApp* app) = 0;
-    /** Virtual destructor of base class */
-    virtual ~InfraredAppScene(){};
-
-private:
-};
-
-/** Start scene
- * Main Infrared application menu
- */
-class InfraredAppSceneStart : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-private:
-    /** Save previously selected submenu index
-     * to highlight it when get back */
-    uint32_t submenu_item_selected = 0;
-};
-
-/** Universal menu scene
- * Scene to select universal remote
- */
-class InfraredAppSceneUniversal : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-private:
-    /** Save previously selected submenu index
-     * to highlight it when get back */
-    uint32_t submenu_item_selected = 0;
-};
-
-/** Learn new signal scene
- * On this scene catching new IR signal performed.
- */
-class InfraredAppSceneLearn : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-/** New signal learn succeeded scene
- */
-class InfraredAppSceneLearnSuccess : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-    bool button_pressed = false;
-};
-
-/** Scene to enter name for new button in remote
- */
-class InfraredAppSceneLearnEnterName : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-/** Scene where signal is learnt
- */
-class InfraredAppSceneLearnDone : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-/** Remote interface scene
- * On this scene you can send IR signals from selected remote
- */
-class InfraredAppSceneRemote : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-private:
-    /** container of button names in current remote. */
-    std::vector<std::string> buttons_names;
-    /** Save previously selected index
-     * to highlight it when get back */
-    uint32_t buttonmenu_item_selected = 0;
-    /** state flag to show button is pressed.
-     * As long as send-signal button pressed no other button
-     * events are handled. */
-    bool button_pressed = false;
-};
-
-/** List of remotes scene
- * Every remote is a file, located on internal/external storage.
- * Every file has same format, and same extension.
- * Files are parsed as you enter 'Remote scene' and showed
- * as a buttons.
- */
-class InfraredAppSceneRemoteList : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-private:
-    /** Save previously selected index
-     * to highlight it when get back */
-    uint32_t submenu_item_selected = 0;
-    /** Remote names to show them in submenu */
-    std::vector<std::string> remote_names;
-};
-
-class InfraredAppSceneAskBack : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-class InfraredAppSceneEdit : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-private:
-    /** Save previously selected index
-     * to highlight it when get back */
-    uint32_t submenu_item_selected = 0;
-};
-
-class InfraredAppSceneEditKeySelect : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-private:
-    /** Button names to show them in submenu */
-    std::vector<std::string> buttons_names;
-};
-
-class InfraredAppSceneEditRename : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-class InfraredAppSceneEditDelete : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-class InfraredAppSceneEditRenameDone : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-class InfraredAppSceneEditDeleteDone : public InfraredAppScene {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-};
-
-class InfraredAppSceneUniversalCommon : public InfraredAppScene {
-    /** Brute force started flag */
-    bool brute_force_started = false;
-
-protected:
-    /** Events handler callback */
-    bool on_event(InfraredApp* app, InfraredAppEvent* event) final;
-    /** Called when exit scene */
-    void on_exit(InfraredApp* app) final;
-
-    /** Show popup window
-     *
-     * @param app - application instance
-     */
-    void show_popup(InfraredApp* app, int record_amount);
-
-    /** Hide popup window
-     *
-     * @param app - application instance
-     */
-    void hide_popup(InfraredApp* app);
-
-    /** Propagate progress in popup window
-     *
-     * @param app - application instance
-     */
-    bool progress_popup(InfraredApp* app);
-
-    /** Item selected callback
-     *
-     * @param context - context
-     * @param index - selected item index
-     */
-    static void infrared_app_item_callback(void* context, uint32_t index);
-
-    /** Brute Force instance */
-    InfraredAppBruteForce brute_force;
-
-    /** Constructor */
-    InfraredAppSceneUniversalCommon(const char* filename)
-        : brute_force(filename) {
-    }
-
-    /** Destructor */
-    ~InfraredAppSceneUniversalCommon() {
-    }
-};
-
-class InfraredAppSceneUniversalTV : public InfraredAppSceneUniversalCommon {
-public:
-    /** Called when enter scene */
-    void on_enter(InfraredApp* app) final;
-
-    /** Constructor
-     * Specifies path to brute force db library */
-    InfraredAppSceneUniversalTV()
-        : InfraredAppSceneUniversalCommon("/ext/infrared/assets/tv.ir") {
-    }
-
-    /** Destructor */
-    ~InfraredAppSceneUniversalTV() {
-    }
-};

+ 0 - 73
applications/infrared/scene/infrared_app_scene_ask_back.cpp

@@ -1,73 +0,0 @@
-#include "../infrared_app.h"
-#include "gui/modules/dialog_ex.h"
-#include "infrared.h"
-#include "infrared/scene/infrared_app_scene.h"
-#include <string>
-
-static void dialog_result_callback(DialogExResult result, void* context) {
-    auto app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::DialogExSelected;
-    event.payload.dialog_ex_result = result;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneAskBack::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    DialogEx* dialog_ex = view_manager->get_dialog_ex();
-
-    if(app->get_learn_new_remote()) {
-        dialog_ex_set_header(dialog_ex, "Exit to Infrared menu?", 64, 0, AlignCenter, AlignTop);
-    } else {
-        dialog_ex_set_header(dialog_ex, "Exit to remote menu?", 64, 0, AlignCenter, AlignTop);
-    }
-
-    dialog_ex_set_text(
-        dialog_ex, "All unsaved data\nwill be lost", 64, 31, AlignCenter, AlignCenter);
-    dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
-    dialog_ex_set_left_button_text(dialog_ex, "Exit");
-    dialog_ex_set_center_button_text(dialog_ex, nullptr);
-    dialog_ex_set_right_button_text(dialog_ex, "Stay");
-    dialog_ex_set_result_callback(dialog_ex, dialog_result_callback);
-    dialog_ex_set_context(dialog_ex, app);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx);
-}
-
-bool InfraredAppSceneAskBack::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::DialogExSelected) {
-        switch(event->payload.dialog_ex_result) {
-        case DialogExResultLeft:
-            consumed = true;
-            if(app->get_learn_new_remote()) {
-                app->search_and_switch_to_previous_scene({InfraredApp::Scene::Start});
-            } else {
-                app->search_and_switch_to_previous_scene(
-                    {InfraredApp::Scene::Edit, InfraredApp::Scene::Remote});
-            }
-            break;
-        case DialogExResultCenter:
-            furi_assert(0);
-            break;
-        case DialogExResultRight:
-            app->switch_to_previous_scene();
-            consumed = true;
-            break;
-        default:
-            break;
-        }
-    }
-
-    if(event->type == InfraredAppEvent::Type::Back) {
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneAskBack::on_exit(InfraredApp*) {
-}

+ 0 - 79
applications/infrared/scene/infrared_app_scene_edit.cpp

@@ -1,79 +0,0 @@
-#include "../infrared_app.h"
-#include "gui/modules/submenu.h"
-
-typedef enum {
-    SubmenuIndexAddKey,
-    SubmenuIndexRenameKey,
-    SubmenuIndexDeleteKey,
-    SubmenuIndexRenameRemote,
-    SubmenuIndexDeleteRemote,
-} SubmenuIndex;
-
-static void submenu_callback(void* context, uint32_t index) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::MenuSelected;
-    event.payload.menu_index = index;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneEdit::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_add_item(submenu, "Add Button", SubmenuIndexAddKey, submenu_callback, app);
-    submenu_add_item(submenu, "Rename Button", SubmenuIndexRenameKey, submenu_callback, app);
-    submenu_add_item(submenu, "Delete Button", SubmenuIndexDeleteKey, submenu_callback, app);
-    submenu_add_item(submenu, "Rename Remote", SubmenuIndexRenameRemote, submenu_callback, app);
-    submenu_add_item(submenu, "Delete Remote", SubmenuIndexDeleteRemote, submenu_callback, app);
-    submenu_set_selected_item(submenu, submenu_item_selected);
-    submenu_item_selected = 0;
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
-}
-
-bool InfraredAppSceneEdit::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::MenuSelected) {
-        submenu_item_selected = event->payload.menu_index;
-        switch(event->payload.menu_index) {
-        case SubmenuIndexAddKey:
-            app->set_learn_new_remote(false);
-            app->switch_to_next_scene(InfraredApp::Scene::Learn);
-            break;
-        case SubmenuIndexRenameKey:
-            app->set_edit_action(InfraredApp::EditAction::Rename);
-            app->set_edit_element(InfraredApp::EditElement::Button);
-            app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect);
-            break;
-        case SubmenuIndexDeleteKey:
-            app->set_edit_action(InfraredApp::EditAction::Delete);
-            app->set_edit_element(InfraredApp::EditElement::Button);
-            app->switch_to_next_scene(InfraredApp::Scene::EditKeySelect);
-            break;
-        case SubmenuIndexRenameRemote:
-            app->set_edit_action(InfraredApp::EditAction::Rename);
-            app->set_edit_element(InfraredApp::EditElement::Remote);
-            app->switch_to_next_scene(InfraredApp::Scene::EditRename);
-            break;
-        case SubmenuIndexDeleteRemote:
-            app->set_edit_action(InfraredApp::EditAction::Delete);
-            app->set_edit_element(InfraredApp::EditElement::Remote);
-            app->switch_to_next_scene(InfraredApp::Scene::EditDelete);
-            break;
-        }
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneEdit::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_reset(submenu);
-}

+ 0 - 100
applications/infrared/scene/infrared_app_scene_edit_delete.cpp

@@ -1,100 +0,0 @@
-#include "../infrared_app.h"
-#include "infrared.h"
-#include "infrared/scene/infrared_app_scene.h"
-#include <string>
-
-static void dialog_result_callback(DialogExResult result, void* context) {
-    auto app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::DialogExSelected;
-    event.payload.dialog_ex_result = result;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneEditDelete::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    DialogEx* dialog_ex = view_manager->get_dialog_ex();
-
-    auto remote_manager = app->get_remote_manager();
-
-    if(app->get_edit_element() == InfraredApp::EditElement::Button) {
-        auto signal = remote_manager->get_button_data(app->get_current_button());
-        dialog_ex_set_header(dialog_ex, "Delete button?", 64, 0, AlignCenter, AlignTop);
-        if(!signal.is_raw()) {
-            auto message = &signal.get_message();
-            app->set_text_store(
-                0,
-                "%s\n%s\nA=0x%0*lX C=0x%0*lX",
-                remote_manager->get_button_name(app->get_current_button()).c_str(),
-                infrared_get_protocol_name(message->protocol),
-                ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
-                message->address,
-                ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
-                message->command);
-        } else {
-            app->set_text_store(
-                0,
-                "%s\nRAW\n%ld samples",
-                remote_manager->get_button_name(app->get_current_button()).c_str(),
-                signal.get_raw_signal().timings_cnt);
-        }
-    } else {
-        dialog_ex_set_header(dialog_ex, "Delete remote?", 64, 0, AlignCenter, AlignTop);
-        app->set_text_store(
-            0,
-            "%s\n with %lu buttons",
-            remote_manager->get_remote_name().c_str(),
-            remote_manager->get_number_of_buttons());
-    }
-
-    dialog_ex_set_text(dialog_ex, app->get_text_store(0), 64, 31, AlignCenter, AlignCenter);
-    dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
-    dialog_ex_set_left_button_text(dialog_ex, "Cancel");
-    dialog_ex_set_right_button_text(dialog_ex, "Delete");
-    dialog_ex_set_result_callback(dialog_ex, dialog_result_callback);
-    dialog_ex_set_context(dialog_ex, app);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx);
-}
-
-bool InfraredAppSceneEditDelete::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::DialogExSelected) {
-        switch(event->payload.dialog_ex_result) {
-        case DialogExResultLeft:
-            app->switch_to_previous_scene();
-            break;
-        case DialogExResultCenter:
-            furi_assert(0);
-            break;
-        case DialogExResultRight: {
-            auto remote_manager = app->get_remote_manager();
-            bool result = false;
-            if(app->get_edit_element() == InfraredApp::EditElement::Remote) {
-                result = remote_manager->delete_remote();
-            } else {
-                result = remote_manager->delete_button(app->get_current_button());
-                app->set_current_button(InfraredApp::ButtonNA);
-            }
-
-            if(!result) {
-                app->search_and_switch_to_previous_scene(
-                    {InfraredApp::Scene::RemoteList, InfraredApp::Scene::Start});
-            } else {
-                app->switch_to_next_scene(InfraredApp::Scene::EditDeleteDone);
-            }
-            break;
-        }
-        default:
-            break;
-        }
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneEditDelete::on_exit(InfraredApp*) {
-}

+ 0 - 38
applications/infrared/scene/infrared_app_scene_edit_delete_done.cpp

@@ -1,38 +0,0 @@
-#include "../infrared_app.h"
-
-void InfraredAppSceneEditDeleteDone::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Popup* popup = view_manager->get_popup();
-
-    popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
-    popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
-
-    popup_set_callback(popup, InfraredApp::popup_callback);
-    popup_set_context(popup, app);
-    popup_set_timeout(popup, 1500);
-    popup_enable_timeout(popup);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
-}
-
-bool InfraredAppSceneEditDeleteDone::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::PopupTimer) {
-        if(app->get_edit_element() == InfraredApp::EditElement::Remote) {
-            app->search_and_switch_to_previous_scene(
-                {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
-        } else {
-            app->search_and_switch_to_previous_scene({InfraredApp::Scene::Remote});
-        }
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneEditDeleteDone::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Popup* popup = view_manager->get_popup();
-    popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop);
-}

+ 0 - 58
applications/infrared/scene/infrared_app_scene_edit_key_select.cpp

@@ -1,58 +0,0 @@
-#include "../infrared_app.h"
-#include "gui/modules/submenu.h"
-
-static void submenu_callback(void* context, uint32_t index) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::MenuSelected;
-    event.payload.menu_index = index;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneEditKeySelect::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-    int item_number = 0;
-
-    const char* header = app->get_edit_action() == InfraredApp::EditAction::Rename ?
-                             "Rename Button:" :
-                             "Delete Button:";
-    submenu_set_header(submenu, header);
-
-    auto remote_manager = app->get_remote_manager();
-    buttons_names = remote_manager->get_button_list();
-    for(const auto& it : buttons_names) {
-        submenu_add_item(submenu, it.c_str(), item_number++, submenu_callback, app);
-    }
-    if((item_number > 0) && (app->get_current_button() != InfraredApp::ButtonNA)) {
-        submenu_set_selected_item(submenu, app->get_current_button());
-        app->set_current_button(InfraredApp::ButtonNA);
-    }
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
-}
-
-bool InfraredAppSceneEditKeySelect::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::MenuSelected) {
-        app->set_current_button(event->payload.menu_index);
-        consumed = true;
-        if(app->get_edit_action() == InfraredApp::EditAction::Rename) {
-            app->switch_to_next_scene(InfraredApp::Scene::EditRename);
-        } else {
-            app->switch_to_next_scene(InfraredApp::Scene::EditDelete);
-        }
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneEditKeySelect::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_reset(submenu);
-}

+ 0 - 83
applications/infrared/scene/infrared_app_scene_edit_rename.cpp

@@ -1,83 +0,0 @@
-#include "../infrared_app.h"
-#include "m-string.h"
-#include "toolbox/path.h"
-
-void InfraredAppSceneEditRename::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    TextInput* text_input = view_manager->get_text_input();
-    size_t enter_name_length = 0;
-
-    auto remote_manager = app->get_remote_manager();
-    if(app->get_edit_element() == InfraredApp::EditElement::Button) {
-        furi_assert(app->get_current_button() != InfraredApp::ButtonNA);
-        auto button_name = remote_manager->get_button_name(app->get_current_button());
-        char* buffer_str = app->get_text_store(0);
-        size_t max_len = InfraredAppRemoteManager::max_button_name_length;
-        strncpy(buffer_str, button_name.c_str(), max_len);
-        buffer_str[max_len + 1] = 0;
-        enter_name_length = max_len;
-        text_input_set_header_text(text_input, "Name the button");
-    } else {
-        auto remote_name = remote_manager->get_remote_name();
-        strncpy(app->get_text_store(0), remote_name.c_str(), app->get_text_store_size());
-        enter_name_length = InfraredAppRemoteManager::max_remote_name_length;
-        text_input_set_header_text(text_input, "Name the remote");
-
-        string_t folder_path;
-        string_init(folder_path);
-
-        if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) {
-            path_extract_dirname(string_get_cstr(app->file_path), folder_path);
-        }
-
-        ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
-            string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str());
-        text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
-
-        string_clear(folder_path);
-    }
-
-    text_input_set_result_callback(
-        text_input,
-        InfraredApp::text_input_callback,
-        app,
-        app->get_text_store(0),
-        enter_name_length,
-        false);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput);
-}
-
-bool InfraredAppSceneEditRename::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::TextEditDone) {
-        auto remote_manager = app->get_remote_manager();
-        bool result = false;
-        if(app->get_edit_element() == InfraredApp::EditElement::Button) {
-            result =
-                remote_manager->rename_button(app->get_current_button(), app->get_text_store(0));
-            app->set_current_button(InfraredApp::ButtonNA);
-        } else {
-            result = remote_manager->rename_remote(app->get_text_store(0));
-        }
-        if(!result) {
-            app->search_and_switch_to_previous_scene(
-                {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
-        } else {
-            app->switch_to_next_scene_without_saving(InfraredApp::Scene::EditRenameDone);
-        }
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneEditRename::on_exit(InfraredApp* app) {
-    TextInput* text_input = app->get_view_manager()->get_text_input();
-
-    void* validator_context = text_input_get_validator_callback_context(text_input);
-    text_input_set_validator(text_input, NULL, NULL);
-
-    if(validator_context != NULL) validator_is_file_free((ValidatorIsFile*)validator_context);
-}

+ 0 - 30
applications/infrared/scene/infrared_app_scene_edit_rename_done.cpp

@@ -1,30 +0,0 @@
-#include "../infrared_app.h"
-
-void InfraredAppSceneEditRenameDone::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Popup* popup = view_manager->get_popup();
-
-    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
-    popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
-
-    popup_set_callback(popup, InfraredApp::popup_callback);
-    popup_set_context(popup, app);
-    popup_set_timeout(popup, 1500);
-    popup_enable_timeout(popup);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
-}
-
-bool InfraredAppSceneEditRenameDone::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::PopupTimer) {
-        app->switch_to_next_scene(InfraredApp::Scene::Remote);
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneEditRenameDone::on_exit(InfraredApp*) {
-}

+ 0 - 76
applications/infrared/scene/infrared_app_scene_learn.cpp

@@ -1,76 +0,0 @@
-#include "../infrared_app.h"
-#include "../infrared_app_event.h"
-#include "infrared.h"
-#include <infrared_worker.h>
-
-static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
-    furi_assert(context);
-    furi_assert(received_signal);
-
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-
-    if(infrared_worker_signal_is_decoded(received_signal)) {
-        InfraredAppSignal signal(infrared_worker_get_decoded_signal(received_signal));
-        app->set_received_signal(signal);
-    } else {
-        const uint32_t* timings;
-        size_t timings_cnt;
-        infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
-        InfraredAppSignal signal(
-            timings, timings_cnt, INFRARED_COMMON_CARRIER_FREQUENCY, INFRARED_COMMON_DUTY_CYCLE);
-        app->set_received_signal(signal);
-    }
-
-    infrared_worker_rx_set_received_signal_callback(app->get_infrared_worker(), NULL, NULL);
-    InfraredAppEvent event;
-    event.type = InfraredAppEvent::Type::InfraredMessageReceived;
-    auto view_manager = app->get_view_manager();
-    view_manager->send_event(&event);
-}
-
-void InfraredAppSceneLearn::on_enter(InfraredApp* app) {
-    auto view_manager = app->get_view_manager();
-    auto popup = view_manager->get_popup();
-
-    auto worker = app->get_infrared_worker();
-    infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, app);
-    infrared_worker_rx_start(worker);
-
-    popup_set_icon(popup, 0, 32, &I_InfraredLearnShort_128x31);
-    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignCenter);
-    popup_set_text(
-        popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter);
-    popup_set_callback(popup, NULL);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
-}
-
-bool InfraredAppSceneLearn::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    switch(event->type) {
-    case InfraredAppEvent::Type::Tick:
-        consumed = true;
-        app->notify_blink_read();
-        break;
-    case InfraredAppEvent::Type::InfraredMessageReceived:
-        app->notify_success();
-        app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnSuccess);
-        break;
-    case InfraredAppEvent::Type::Back:
-        consumed = true;
-        app->switch_to_previous_scene();
-        break;
-    default:
-        furi_assert(0);
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneLearn::on_exit(InfraredApp* app) {
-    infrared_worker_rx_stop(app->get_infrared_worker());
-    auto view_manager = app->get_view_manager();
-    auto popup = view_manager->get_popup();
-    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter);
-}

+ 0 - 41
applications/infrared/scene/infrared_app_scene_learn_done.cpp

@@ -1,41 +0,0 @@
-#include "../infrared_app.h"
-#include <dolphin/dolphin.h>
-
-void InfraredAppSceneLearnDone::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Popup* popup = view_manager->get_popup();
-
-    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
-    DOLPHIN_DEED(DolphinDeedIrSave);
-
-    if(app->get_learn_new_remote()) {
-        popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
-    } else {
-        popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
-    }
-
-    popup_set_callback(popup, InfraredApp::popup_callback);
-    popup_set_context(popup, app);
-    popup_set_timeout(popup, 1500);
-    popup_enable_timeout(popup);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Popup);
-}
-
-bool InfraredAppSceneLearnDone::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::PopupTimer) {
-        app->switch_to_next_scene(InfraredApp::Scene::Remote);
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneLearnDone::on_exit(InfraredApp* app) {
-    app->set_learn_new_remote(false);
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Popup* popup = view_manager->get_popup();
-    popup_set_header(popup, nullptr, 0, 0, AlignLeft, AlignTop);
-}

+ 0 - 60
applications/infrared/scene/infrared_app_scene_learn_enter_name.cpp

@@ -1,60 +0,0 @@
-#include "../infrared_app.h"
-#include "gui/modules/text_input.h"
-
-void InfraredAppSceneLearnEnterName::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    TextInput* text_input = view_manager->get_text_input();
-
-    auto signal = app->get_received_signal();
-
-    if(!signal.is_raw()) {
-        auto message = &signal.get_message();
-        app->set_text_store(
-            0,
-            "%.4s_%0*lX",
-            infrared_get_protocol_name(message->protocol),
-            ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
-            message->command);
-    } else {
-        auto raw_signal = signal.get_raw_signal();
-        app->set_text_store(0, "RAW_%d", raw_signal.timings_cnt);
-    }
-
-    text_input_set_header_text(text_input, "Name the button");
-    text_input_set_result_callback(
-        text_input,
-        InfraredApp::text_input_callback,
-        app,
-        app->get_text_store(0),
-        InfraredAppRemoteManager::max_button_name_length,
-        true);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::TextInput);
-}
-
-bool InfraredAppSceneLearnEnterName::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::TextEditDone) {
-        auto remote_manager = app->get_remote_manager();
-        bool result = false;
-        if(app->get_learn_new_remote()) {
-            result = remote_manager->add_remote_with_button(
-                app->get_text_store(0), app->get_received_signal());
-        } else {
-            result =
-                remote_manager->add_button(app->get_text_store(0), app->get_received_signal());
-        }
-
-        if(!result) {
-            app->search_and_switch_to_previous_scene(
-                {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
-        } else {
-            app->switch_to_next_scene_without_saving(InfraredApp::Scene::LearnDone);
-        }
-    }
-    return consumed;
-}
-
-void InfraredAppSceneLearnEnterName::on_exit(InfraredApp*) {
-}

+ 0 - 142
applications/infrared/scene/infrared_app_scene_learn_success.cpp

@@ -1,142 +0,0 @@
-#include <gui/modules/dialog_ex.h>
-#include <memory>
-#include <dolphin/dolphin.h>
-
-#include "../infrared_app.h"
-#include "infrared.h"
-
-static void dialog_result_callback(DialogExResult result, void* context) {
-    auto app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::DialogExSelected;
-    event.payload.dialog_ex_result = result;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneLearnSuccess::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    DialogEx* dialog_ex = view_manager->get_dialog_ex();
-
-    DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
-    app->notify_green_on();
-
-    infrared_worker_tx_set_get_signal_callback(
-        app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app);
-    infrared_worker_tx_set_signal_sent_callback(
-        app->get_infrared_worker(), InfraredApp::signal_sent_callback, app);
-
-    auto signal = app->get_received_signal();
-
-    if(!signal.is_raw()) {
-        auto message = &signal.get_message();
-        uint8_t adr_digits =
-            ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4);
-        uint8_t cmd_digits =
-            ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4);
-        uint8_t max_digits = MAX(adr_digits, cmd_digits);
-        max_digits = MIN(max_digits, 7);
-        size_t label_x_offset = 63 + (7 - max_digits) * 3;
-
-        app->set_text_store(0, "%s", infrared_get_protocol_name(message->protocol));
-        app->set_text_store(
-            1,
-            "A: 0x%0*lX\nC: 0x%0*lX\n",
-            adr_digits,
-            message->address,
-            cmd_digits,
-            message->command);
-
-        dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 7, AlignCenter, AlignCenter);
-        dialog_ex_set_text(
-            dialog_ex, app->get_text_store(1), label_x_offset, 34, AlignLeft, AlignCenter);
-    } else {
-        dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter);
-        app->set_text_store(0, "%d samples", signal.get_raw_signal().timings_cnt);
-        dialog_ex_set_text(dialog_ex, app->get_text_store(0), 75, 23, AlignLeft, AlignTop);
-    }
-
-    dialog_ex_set_left_button_text(dialog_ex, "Retry");
-    dialog_ex_set_right_button_text(dialog_ex, "Save");
-    dialog_ex_set_center_button_text(dialog_ex, "Send");
-    dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
-    dialog_ex_set_result_callback(dialog_ex, dialog_result_callback);
-    dialog_ex_set_context(dialog_ex, app);
-    dialog_ex_enable_extended_events(dialog_ex);
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::DialogEx);
-}
-
-bool InfraredAppSceneLearnSuccess::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-    if(event->type == InfraredAppEvent::Type::Tick) {
-        /* Send event every tick to suppress any switching off green light */
-        if(!button_pressed) {
-            app->notify_green_on();
-        }
-    }
-
-    if(event->type == InfraredAppEvent::Type::DialogExSelected) {
-        switch(event->payload.dialog_ex_result) {
-        case DialogExResultLeft:
-            consumed = true;
-            if(!button_pressed) {
-                app->switch_to_next_scene_without_saving(InfraredApp::Scene::Learn);
-            }
-            break;
-        case DialogExResultRight: {
-            consumed = true;
-            if(!button_pressed) {
-                app->switch_to_next_scene(InfraredApp::Scene::LearnEnterName);
-            }
-            break;
-        }
-        case DialogExPressCenter:
-            if(!button_pressed) {
-                button_pressed = true;
-
-                auto signal = app->get_received_signal();
-                if(signal.is_raw()) {
-                    infrared_worker_set_raw_signal(
-                        app->get_infrared_worker(),
-                        signal.get_raw_signal().timings,
-                        signal.get_raw_signal().timings_cnt);
-                } else {
-                    infrared_worker_set_decoded_signal(
-                        app->get_infrared_worker(), &signal.get_message());
-                }
-
-                infrared_worker_tx_start(app->get_infrared_worker());
-            }
-            break;
-        case DialogExReleaseCenter:
-            if(button_pressed) {
-                button_pressed = false;
-                infrared_worker_tx_stop(app->get_infrared_worker());
-                app->notify_green_off();
-            }
-            break;
-        default:
-            break;
-        }
-    }
-
-    if(event->type == InfraredAppEvent::Type::Back) {
-        if(!button_pressed) {
-            app->switch_to_next_scene(InfraredApp::Scene::AskBack);
-        }
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneLearnSuccess::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    DialogEx* dialog_ex = view_manager->get_dialog_ex();
-    dialog_ex_reset(dialog_ex);
-    app->notify_green_off();
-    infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr);
-    infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr);
-}

+ 0 - 131
applications/infrared/scene/infrared_app_scene_remote.cpp

@@ -1,131 +0,0 @@
-#include <gui/modules/button_menu.h>
-#include <input/input.h>
-#include <infrared_worker.h>
-#include <dolphin/dolphin.h>
-#include "../infrared_app.h"
-#include "../infrared_app_view_manager.h"
-
-typedef enum {
-    ButtonIndexPlus = -2,
-    ButtonIndexEdit = -1,
-    ButtonIndexNA = 0,
-} ButtonIndex;
-
-static void button_menu_callback(void* context, int32_t index, InputType type) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    if(type == InputTypePress) {
-        event.type = InfraredAppEvent::Type::MenuSelectedPress;
-    } else if(type == InputTypeRelease) {
-        event.type = InfraredAppEvent::Type::MenuSelectedRelease;
-    } else if(type == InputTypeShort) {
-        event.type = InfraredAppEvent::Type::MenuSelected;
-    } else {
-        furi_assert(0);
-    }
-
-    event.payload.menu_index = index;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneRemote::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    ButtonMenu* button_menu = view_manager->get_button_menu();
-    auto remote_manager = app->get_remote_manager();
-    int i = 0;
-    button_pressed = false;
-
-    infrared_worker_tx_set_get_signal_callback(
-        app->get_infrared_worker(), infrared_worker_tx_get_signal_steady_callback, app);
-    infrared_worker_tx_set_signal_sent_callback(
-        app->get_infrared_worker(), InfraredApp::signal_sent_callback, app);
-    buttons_names = remote_manager->get_button_list();
-
-    i = 0;
-    for(auto& name : buttons_names) {
-        button_menu_add_item(
-            button_menu, name.c_str(), i++, button_menu_callback, ButtonMenuItemTypeCommon, app);
-    }
-
-    button_menu_add_item(
-        button_menu, "+", ButtonIndexPlus, button_menu_callback, ButtonMenuItemTypeControl, app);
-    button_menu_add_item(
-        button_menu, "Edit", ButtonIndexEdit, button_menu_callback, ButtonMenuItemTypeControl, app);
-
-    app->set_text_store(0, "%s", remote_manager->get_remote_name().c_str());
-    button_menu_set_header(button_menu, app->get_text_store(0));
-    if(buttonmenu_item_selected != ButtonIndexNA) {
-        button_menu_set_selected_item(button_menu, buttonmenu_item_selected);
-        buttonmenu_item_selected = ButtonIndexNA;
-    }
-    view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu);
-}
-
-bool InfraredAppSceneRemote::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = true;
-
-    if((event->type == InfraredAppEvent::Type::MenuSelected) ||
-       (event->type == InfraredAppEvent::Type::MenuSelectedPress) ||
-       (event->type == InfraredAppEvent::Type::MenuSelectedRelease)) {
-        switch(event->payload.menu_index) {
-        case ButtonIndexPlus:
-            furi_assert(event->type == InfraredAppEvent::Type::MenuSelected);
-            buttonmenu_item_selected = event->payload.menu_index;
-            app->set_learn_new_remote(false);
-            app->switch_to_next_scene(InfraredApp::Scene::Learn);
-            break;
-        case ButtonIndexEdit:
-            furi_assert(event->type == InfraredAppEvent::Type::MenuSelected);
-            buttonmenu_item_selected = event->payload.menu_index;
-            app->switch_to_next_scene(InfraredApp::Scene::Edit);
-            break;
-        default:
-            furi_assert(event->type != InfraredAppEvent::Type::MenuSelected);
-            bool pressed = (event->type == InfraredAppEvent::Type::MenuSelectedPress);
-
-            if(pressed && !button_pressed) {
-                button_pressed = true;
-
-                auto button_signal =
-                    app->get_remote_manager()->get_button_data(event->payload.menu_index);
-                if(button_signal.is_raw()) {
-                    infrared_worker_set_raw_signal(
-                        app->get_infrared_worker(),
-                        button_signal.get_raw_signal().timings,
-                        button_signal.get_raw_signal().timings_cnt);
-                } else {
-                    infrared_worker_set_decoded_signal(
-                        app->get_infrared_worker(), &button_signal.get_message());
-                }
-
-                DOLPHIN_DEED(DolphinDeedIrSend);
-                infrared_worker_tx_start(app->get_infrared_worker());
-            } else if(!pressed && button_pressed) {
-                button_pressed = false;
-                infrared_worker_tx_stop(app->get_infrared_worker());
-                app->notify_green_off();
-            }
-            break;
-        }
-    } else if(event->type == InfraredAppEvent::Type::Back) {
-        if(!button_pressed) {
-            app->search_and_switch_to_previous_scene(
-                {InfraredApp::Scene::Start, InfraredApp::Scene::RemoteList});
-        }
-    } else {
-        consumed = false;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneRemote::on_exit(InfraredApp* app) {
-    infrared_worker_tx_set_get_signal_callback(app->get_infrared_worker(), nullptr, nullptr);
-    infrared_worker_tx_set_signal_sent_callback(app->get_infrared_worker(), nullptr, nullptr);
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    ButtonMenu* button_menu = view_manager->get_button_menu();
-
-    button_menu_reset(button_menu);
-}

+ 0 - 45
applications/infrared/scene/infrared_app_scene_remote_list.cpp

@@ -1,45 +0,0 @@
-#include "../infrared_app.h"
-#include "assets_icons.h"
-#include "infrared/infrared_app_event.h"
-#include <text_store.h>
-
-void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) {
-    furi_assert(app);
-
-    bool result = false;
-    bool file_select_result;
-    auto remote_manager = app->get_remote_manager();
-    DialogsApp* dialogs = app->get_dialogs();
-
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    ButtonMenu* button_menu = view_manager->get_button_menu();
-    button_menu_reset(button_menu);
-    view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu);
-
-    file_select_result = dialog_file_browser_show(
-        dialogs,
-        app->file_path,
-        app->file_path,
-        InfraredApp::infrared_extension,
-        true,
-        &I_ir_10px,
-        true);
-
-    if(file_select_result) {
-        if(remote_manager->load(app->file_path)) {
-            app->switch_to_next_scene(InfraredApp::Scene::Remote);
-            result = true;
-        }
-    }
-
-    if(!result) {
-        app->switch_to_previous_scene();
-    }
-}
-
-bool InfraredAppSceneRemoteList::on_event(InfraredApp*, InfraredAppEvent*) {
-    return false;
-}
-
-void InfraredAppSceneRemoteList::on_exit(InfraredApp*) {
-}

+ 0 - 68
applications/infrared/scene/infrared_app_scene_start.cpp

@@ -1,68 +0,0 @@
-#include "../infrared_app.h"
-
-typedef enum {
-    SubmenuIndexUniversalLibrary,
-    SubmenuIndexLearnNewRemote,
-    SubmenuIndexSavedRemotes,
-} SubmenuIndex;
-
-static void submenu_callback(void* context, uint32_t index) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::MenuSelected;
-    event.payload.menu_index = index;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneStart::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_add_item(
-        submenu, "Universal Library", SubmenuIndexUniversalLibrary, submenu_callback, app);
-    submenu_add_item(
-        submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app);
-    submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app);
-    submenu_set_selected_item(submenu, submenu_item_selected);
-
-    string_set_str(app->file_path, InfraredApp::infrared_directory);
-    submenu_item_selected = 0;
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
-}
-
-bool InfraredAppSceneStart::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::MenuSelected) {
-        submenu_item_selected = event->payload.menu_index;
-        switch(event->payload.menu_index) {
-        case SubmenuIndexUniversalLibrary:
-            app->switch_to_next_scene(InfraredApp::Scene::Universal);
-            break;
-        case SubmenuIndexLearnNewRemote:
-            app->set_learn_new_remote(true);
-            app->switch_to_next_scene(InfraredApp::Scene::Learn);
-            break;
-        case SubmenuIndexSavedRemotes:
-            app->switch_to_next_scene(InfraredApp::Scene::RemoteList);
-            break;
-        default:
-            furi_assert(0);
-            break;
-        }
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneStart::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    app->get_remote_manager()->reset_remote();
-    submenu_reset(submenu);
-}

+ 0 - 57
applications/infrared/scene/infrared_app_scene_universal.cpp

@@ -1,57 +0,0 @@
-#include "../infrared_app.h"
-
-typedef enum {
-    SubmenuIndexUniversalTV,
-    SubmenuIndexUniversalAudio,
-    SubmenuIndexUniversalAirConditioner,
-} SubmenuIndex;
-
-static void submenu_callback(void* context, uint32_t index) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::MenuSelected;
-    event.payload.menu_index = index;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-void InfraredAppSceneUniversal::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_add_item(submenu, "TVs", SubmenuIndexUniversalTV, submenu_callback, app);
-    submenu_set_selected_item(submenu, submenu_item_selected);
-    submenu_item_selected = 0;
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
-}
-
-bool InfraredAppSceneUniversal::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(event->type == InfraredAppEvent::Type::MenuSelected) {
-        submenu_item_selected = event->payload.menu_index;
-        switch(event->payload.menu_index) {
-        case SubmenuIndexUniversalTV:
-            app->switch_to_next_scene(InfraredApp::Scene::UniversalTV);
-            break;
-        case SubmenuIndexUniversalAudio:
-            //            app->switch_to_next_scene(InfraredApp::Scene::UniversalAudio);
-            break;
-        case SubmenuIndexUniversalAirConditioner:
-            //            app->switch_to_next_scene(InfraredApp::Scene::UniversalAirConditioner);
-            break;
-        }
-        consumed = true;
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneUniversal::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_reset(submenu);
-}

+ 0 - 107
applications/infrared/scene/infrared_app_scene_universal_common.cpp

@@ -1,107 +0,0 @@
-#include <dolphin/dolphin.h>
-#include <gui/modules/button_menu.h>
-#include <gui/modules/button_panel.h>
-#include <gui/view.h>
-#include <gui/view_stack.h>
-
-#include "../infrared_app.h"
-#include "infrared/infrared_app_event.h"
-#include "infrared/infrared_app_view_manager.h"
-#include "infrared/scene/infrared_app_scene.h"
-#include "../view/infrared_progress_view.h"
-
-void InfraredAppSceneUniversalCommon::infrared_app_item_callback(void* context, uint32_t index) {
-    InfraredApp* app = static_cast<InfraredApp*>(context);
-    InfraredAppEvent event;
-
-    event.type = InfraredAppEvent::Type::ButtonPanelPressed;
-    event.payload.menu_index = index;
-
-    app->get_view_manager()->send_event(&event);
-}
-
-static void infrared_progress_back_callback(void* context) {
-    furi_assert(context);
-    auto app = static_cast<InfraredApp*>(context);
-
-    InfraredAppEvent infrared_event = {
-        .payload = {.dummy = 0},
-        .type = InfraredAppEvent::Type::Back,
-    };
-    app->get_view_manager()->clear_events();
-    app->get_view_manager()->send_event(&infrared_event);
-}
-
-void InfraredAppSceneUniversalCommon::hide_popup(InfraredApp* app) {
-    auto stack_view = app->get_view_manager()->get_universal_view_stack();
-    auto progress_view = app->get_view_manager()->get_progress();
-    view_stack_remove_view(stack_view, infrared_progress_view_get_view(progress_view));
-}
-
-void InfraredAppSceneUniversalCommon::show_popup(InfraredApp* app, int record_amount) {
-    auto stack_view = app->get_view_manager()->get_universal_view_stack();
-    auto progress_view = app->get_view_manager()->get_progress();
-    infrared_progress_view_set_progress_total(progress_view, record_amount);
-    infrared_progress_view_set_back_callback(progress_view, infrared_progress_back_callback, app);
-    view_stack_add_view(stack_view, infrared_progress_view_get_view(progress_view));
-}
-
-bool InfraredAppSceneUniversalCommon::progress_popup(InfraredApp* app) {
-    auto progress_view = app->get_view_manager()->get_progress();
-    return infrared_progress_view_increase_progress(progress_view);
-}
-
-bool InfraredAppSceneUniversalCommon::on_event(InfraredApp* app, InfraredAppEvent* event) {
-    bool consumed = false;
-
-    if(brute_force_started) {
-        if(event->type == InfraredAppEvent::Type::Tick) {
-            auto view_manager = app->get_view_manager();
-            app->notify_blink_send();
-            InfraredAppEvent tick_event = {
-                .payload = {.dummy = 0},
-                .type = InfraredAppEvent::Type::Tick,
-            };
-            view_manager->send_event(&tick_event);
-            bool result = brute_force.send_next_bruteforce();
-            if(result) {
-                result = progress_popup(app);
-            }
-            if(!result) {
-                brute_force.stop_bruteforce();
-                brute_force_started = false;
-                hide_popup(app);
-            }
-            consumed = true;
-        } else if(event->type == InfraredAppEvent::Type::Back) {
-            brute_force_started = false;
-            brute_force.stop_bruteforce();
-            hide_popup(app);
-            consumed = true;
-        }
-    } else {
-        if(event->type == InfraredAppEvent::Type::ButtonPanelPressed) {
-            int record_amount = 0;
-            if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) {
-                DOLPHIN_DEED(DolphinDeedIrBruteForce);
-                brute_force_started = true;
-                show_popup(app, record_amount);
-                app->notify_blink_send();
-            } else {
-                app->switch_to_previous_scene();
-            }
-            consumed = true;
-        } else if(event->type == InfraredAppEvent::Type::Back) {
-            app->switch_to_previous_scene();
-            consumed = true;
-        }
-    }
-
-    return consumed;
-}
-
-void InfraredAppSceneUniversalCommon::on_exit(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    ButtonPanel* button_panel = view_manager->get_button_panel();
-    button_panel_reset(button_panel);
-}

+ 0 - 123
applications/infrared/scene/infrared_app_scene_universal_tv.cpp

@@ -1,123 +0,0 @@
-#include <stdint.h>
-#include <gui/modules/loading.h>
-#include <gui/view_stack.h>
-#include "infrared/scene/infrared_app_scene.h"
-#include "infrared/infrared_app.h"
-
-void InfraredAppSceneUniversalTV::on_enter(InfraredApp* app) {
-    InfraredAppViewManager* view_manager = app->get_view_manager();
-    ButtonPanel* button_panel = view_manager->get_button_panel();
-    button_panel_reserve(button_panel, 2, 3);
-
-    int i = 0;
-    button_panel_add_item(
-        button_panel,
-        i,
-        0,
-        0,
-        3,
-        19,
-        &I_Power_25x27,
-        &I_Power_hvr_25x27,
-        infrared_app_item_callback,
-        app);
-    brute_force.add_record(i, "POWER");
-    ++i;
-    button_panel_add_item(
-        button_panel,
-        i,
-        1,
-        0,
-        36,
-        19,
-        &I_Mute_25x27,
-        &I_Mute_hvr_25x27,
-        infrared_app_item_callback,
-        app);
-    brute_force.add_record(i, "MUTE");
-    ++i;
-    button_panel_add_item(
-        button_panel,
-        i,
-        0,
-        1,
-        3,
-        66,
-        &I_Vol_up_25x27,
-        &I_Vol_up_hvr_25x27,
-        infrared_app_item_callback,
-        app);
-    brute_force.add_record(i, "VOL+");
-    ++i;
-    button_panel_add_item(
-        button_panel,
-        i,
-        1,
-        1,
-        36,
-        66,
-        &I_Up_25x27,
-        &I_Up_hvr_25x27,
-        infrared_app_item_callback,
-        app);
-    brute_force.add_record(i, "CH+");
-    ++i;
-    button_panel_add_item(
-        button_panel,
-        i,
-        0,
-        2,
-        3,
-        98,
-        &I_Vol_down_25x27,
-        &I_Vol_down_hvr_25x27,
-        infrared_app_item_callback,
-        app);
-    brute_force.add_record(i, "VOL-");
-    ++i;
-    button_panel_add_item(
-        button_panel,
-        i,
-        1,
-        2,
-        36,
-        98,
-        &I_Down_25x27,
-        &I_Down_hvr_25x27,
-        infrared_app_item_callback,
-        app);
-    brute_force.add_record(i, "CH-");
-
-    button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote");
-    button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol");
-    button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch");
-
-    view_manager->switch_to(InfraredAppViewManager::ViewId::UniversalRemote);
-
-    auto stack_view = app->get_view_manager()->get_universal_view_stack();
-    auto loading_view = app->get_view_manager()->get_loading();
-    view_stack_add_view(stack_view, loading_get_view(loading_view));
-
-    /**
-     * Problem: Update events are not handled in Loading View, because:
-     * 1) Timer task has least prio
-     * 2) Storage service uses drivers that capture whole CPU time
-     *      to handle SD communication
-     *
-     * Ugly workaround, but it works for current situation:
-     * raise timer task prio for DB scanning period.
-     */
-    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
-    TaskHandle_t storage_task = xTaskGetHandle("StorageSrv");
-    uint32_t timer_prio = uxTaskPriorityGet(timer_task);
-    uint32_t storage_prio = uxTaskPriorityGet(storage_task);
-    vTaskPrioritySet(timer_task, storage_prio + 1);
-    bool result = brute_force.calculate_messages();
-    vTaskPrioritySet(timer_task, timer_prio);
-
-    view_stack_remove_view(stack_view, loading_get_view(loading_view));
-
-    if(!result) {
-        app->switch_to_previous_scene();
-    }
-}

+ 92 - 0
applications/infrared/scenes/common/infrared_scene_universal_common.c

@@ -0,0 +1,92 @@
+#include "../../infrared_i.h"
+
+#include <dolphin/dolphin.h>
+
+void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
+    Infrared* infrared = context;
+    uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
+}
+
+static void infrared_scene_universal_common_progress_back_callback(void* context) {
+    Infrared* infrared = context;
+    uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
+}
+
+static void infrared_scene_universal_common_show_popup(Infrared* infrared, uint32_t record_count) {
+    ViewStack* view_stack = infrared->view_stack;
+    InfraredProgressView* progress = infrared->progress;
+    infrared_progress_view_set_progress_total(progress, record_count);
+    infrared_progress_view_set_back_callback(
+        progress, infrared_scene_universal_common_progress_back_callback, infrared);
+    view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
+}
+
+static void infrared_scene_universal_common_hide_popup(Infrared* infrared) {
+    ViewStack* view_stack = infrared->view_stack;
+    InfraredProgressView* progress = infrared->progress;
+    view_stack_remove_view(view_stack, infrared_progress_view_get_view(progress));
+}
+
+void infrared_scene_universal_common_on_enter(void* context) {
+    Infrared* infrared = context;
+    view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel));
+}
+
+bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    InfraredBruteForce* brute_force = infrared->brute_force;
+    bool consumed = false;
+
+    if(infrared_brute_force_is_started(brute_force)) {
+        if(event.type == SceneManagerEventTypeTick) {
+            infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkSend);
+            bool success = infrared_brute_force_send_next(brute_force);
+            if(success) {
+                success = infrared_progress_view_increase_progress(infrared->progress);
+            }
+            if(!success) {
+                infrared_brute_force_stop(brute_force);
+                infrared_scene_universal_common_hide_popup(infrared);
+            }
+            consumed = true;
+        } else if(event.type == SceneManagerEventTypeCustom) {
+            if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
+                infrared_brute_force_stop(brute_force);
+                infrared_scene_universal_common_hide_popup(infrared);
+                consumed = true;
+            }
+        }
+    } else {
+        if(event.type == SceneManagerEventTypeBack) {
+            scene_manager_previous_scene(scene_manager);
+            consumed = true;
+        } else if(event.type == SceneManagerEventTypeCustom) {
+            if(infrared_custom_event_get_type(event.event) ==
+               InfraredCustomEventTypeButtonSelected) {
+                uint32_t record_count;
+                if(infrared_brute_force_start(
+                       brute_force, infrared_custom_event_get_value(event.event), &record_count)) {
+                    DOLPHIN_DEED(DolphinDeedIrBruteForce);
+                    infrared_scene_universal_common_show_popup(infrared, record_count);
+                    infrared_play_notification_message(
+                        infrared, InfraredNotificationMessageBlinkSend);
+                } else {
+                    scene_manager_previous_scene(scene_manager);
+                }
+                consumed = true;
+            }
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_universal_common_on_exit(void* context) {
+    Infrared* infrared = context;
+    ButtonPanel* button_panel = infrared->button_panel;
+    view_stack_remove_view(infrared->view_stack, button_panel_get_view(button_panel));
+    button_panel_reset(button_panel);
+}

+ 8 - 0
applications/infrared/scenes/common/infrared_scene_universal_common.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+void infrared_scene_universal_common_on_enter(void* context);
+bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event);
+void infrared_scene_universal_common_on_exit(void* context);
+void infrared_scene_universal_common_item_callback(void* context, uint32_t index);

+ 30 - 0
applications/infrared/scenes/infrared_scene.c

@@ -0,0 +1,30 @@
+#include "infrared_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const infrared_on_enter_handlers[])(void*) = {
+#include "infrared_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 infrared_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "infrared_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 infrared_on_exit_handlers[])(void* context) = {
+#include "infrared_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers infrared_scene_handlers = {
+    .on_enter_handlers = infrared_on_enter_handlers,
+    .on_event_handlers = infrared_on_event_handlers,
+    .on_exit_handlers = infrared_on_exit_handlers,
+    .scene_num = InfraredSceneNum,
+};

+ 29 - 0
applications/infrared/scenes/infrared_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) InfraredScene##id,
+typedef enum {
+#include "infrared_scene_config.h"
+    InfraredSceneNum,
+} InfraredScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers infrared_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "infrared_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 "infrared_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 "infrared_scene_config.h"
+#undef ADD_SCENE

+ 59 - 0
applications/infrared/scenes/infrared_scene_ask_back.c

@@ -0,0 +1,59 @@
+#include "../infrared_i.h"
+
+static void infrared_scene_dialog_result_callback(DialogExResult result, void* context) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
+}
+
+void infrared_scene_ask_back_on_enter(void* context) {
+    Infrared* infrared = context;
+    DialogEx* dialog_ex = infrared->dialog_ex;
+
+    if(infrared->app_state.is_learning_new_remote) {
+        dialog_ex_set_header(dialog_ex, "Exit to Infrared menu?", 64, 0, AlignCenter, AlignTop);
+    } else {
+        dialog_ex_set_header(dialog_ex, "Exit to remote menu?", 64, 0, AlignCenter, AlignTop);
+    }
+
+    dialog_ex_set_text(
+        dialog_ex, "All unsaved data\nwill be lost", 64, 31, AlignCenter, AlignCenter);
+    dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
+    dialog_ex_set_left_button_text(dialog_ex, "Exit");
+    dialog_ex_set_center_button_text(dialog_ex, NULL);
+    dialog_ex_set_right_button_text(dialog_ex, "Stay");
+    dialog_ex_set_result_callback(dialog_ex, infrared_scene_dialog_result_callback);
+    dialog_ex_set_context(dialog_ex, context);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
+}
+
+bool infrared_scene_ask_back_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultLeft) {
+            if(infrared->app_state.is_learning_new_remote) {
+                scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, InfraredSceneStart);
+            } else {
+                scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, InfraredSceneRemote);
+            }
+            consumed = true;
+        } else if(event.event == DialogExResultRight) {
+            scene_manager_previous_scene(scene_manager);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_ask_back_on_exit(void* context) {
+    Infrared* infrared = context;
+    dialog_ex_reset(infrared->dialog_ex);
+}

+ 17 - 0
applications/infrared/scenes/infrared_scene_config.h

@@ -0,0 +1,17 @@
+ADD_SCENE(infrared, start, Start)
+ADD_SCENE(infrared, ask_back, AskBack)
+ADD_SCENE(infrared, edit, Edit)
+ADD_SCENE(infrared, edit_delete, EditDelete)
+ADD_SCENE(infrared, edit_delete_done, EditDeleteDone)
+ADD_SCENE(infrared, edit_button_select, EditButtonSelect)
+ADD_SCENE(infrared, edit_rename, EditRename)
+ADD_SCENE(infrared, edit_rename_done, EditRenameDone)
+ADD_SCENE(infrared, learn, Learn)
+ADD_SCENE(infrared, learn_done, LearnDone)
+ADD_SCENE(infrared, learn_enter_name, LearnEnterName)
+ADD_SCENE(infrared, learn_success, LearnSuccess)
+ADD_SCENE(infrared, remote, Remote)
+ADD_SCENE(infrared, remote_list, RemoteList)
+ADD_SCENE(infrared, universal, Universal)
+ADD_SCENE(infrared, universal_tv, UniversalTV)
+ADD_SCENE(infrared, debug, Debug)

+ 68 - 0
applications/infrared/scenes/infrared_scene_debug.c

@@ -0,0 +1,68 @@
+#include "../infrared_i.h"
+
+void infrared_scene_debug_on_enter(void* context) {
+    Infrared* infrared = context;
+    InfraredWorker* worker = infrared->worker;
+
+    infrared_worker_rx_set_received_signal_callback(
+        worker, infrared_signal_received_callback, context);
+    infrared_worker_rx_enable_blink_on_receiving(worker, true);
+    infrared_worker_rx_start(worker);
+
+    infrared_debug_view_set_text(infrared->debug_view, "Received signals\nwill appear here");
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDebugView);
+}
+
+bool infrared_scene_debug_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypeSignalReceived) {
+            InfraredDebugView* debug_view = infrared->debug_view;
+            InfraredSignal* signal = infrared->received_signal;
+
+            if(infrared_signal_is_raw(signal)) {
+                InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
+                infrared_debug_view_set_text(debug_view, "RAW\n%d samples\n", raw->timings_size);
+
+                printf("RAW, %d samples:\r\n", raw->timings_size);
+                for(size_t i = 0; i < raw->timings_size; ++i) {
+                    printf("%lu ", raw->timings[i]);
+                }
+                printf("\r\n");
+
+            } else {
+                InfraredMessage* message = infrared_signal_get_message(signal);
+                infrared_debug_view_set_text(
+                    debug_view,
+                    "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n",
+                    infrared_get_protocol_name(message->protocol),
+                    ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
+                    message->address,
+                    ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
+                    message->command,
+                    message->repeat ? " R" : "");
+
+                printf(
+                    "== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n",
+                    infrared_get_protocol_name(message->protocol),
+                    ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
+                    message->address,
+                    ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
+                    message->command,
+                    message->repeat ? " R" : "");
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_debug_on_exit(void* context) {
+    Infrared* infrared = context;
+    InfraredWorker* worker = infrared->worker;
+    infrared_worker_rx_stop(worker);
+    infrared_worker_rx_enable_blink_on_receiving(worker, false);
+}

+ 101 - 0
applications/infrared/scenes/infrared_scene_edit.c

@@ -0,0 +1,101 @@
+#include "../infrared_i.h"
+
+typedef enum {
+    SubmenuIndexAddButton,
+    SubmenuIndexRenameButton,
+    SubmenuIndexDeleteButton,
+    SubmenuIndexRenameRemote,
+    SubmenuIndexDeleteRemote,
+} SubmenuIndex;
+
+static void infrared_scene_edit_submenu_callback(void* context, uint32_t index) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
+}
+
+void infrared_scene_edit_on_enter(void* context) {
+    Infrared* infrared = context;
+    Submenu* submenu = infrared->submenu;
+    SceneManager* scene_manager = infrared->scene_manager;
+
+    submenu_add_item(
+        submenu,
+        "Add Button",
+        SubmenuIndexAddButton,
+        infrared_scene_edit_submenu_callback,
+        context);
+    submenu_add_item(
+        submenu,
+        "Rename Button",
+        SubmenuIndexRenameButton,
+        infrared_scene_edit_submenu_callback,
+        context);
+    submenu_add_item(
+        submenu,
+        "Delete Button",
+        SubmenuIndexDeleteButton,
+        infrared_scene_edit_submenu_callback,
+        context);
+    submenu_add_item(
+        submenu,
+        "Rename Remote",
+        SubmenuIndexRenameRemote,
+        infrared_scene_edit_submenu_callback,
+        context);
+    submenu_add_item(
+        submenu,
+        "Delete Remote",
+        SubmenuIndexDeleteRemote,
+        infrared_scene_edit_submenu_callback,
+        context);
+
+    const uint32_t submenu_index = scene_manager_get_scene_state(scene_manager, InfraredSceneEdit);
+    submenu_set_selected_item(submenu, submenu_index);
+    scene_manager_set_scene_state(scene_manager, InfraredSceneEdit, SubmenuIndexAddButton);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
+}
+
+bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        const uint32_t submenu_index = event.event;
+        scene_manager_set_scene_state(scene_manager, InfraredSceneEdit, submenu_index);
+
+        if(submenu_index == SubmenuIndexAddButton) {
+            infrared->app_state.is_learning_new_remote = false;
+            scene_manager_next_scene(scene_manager, InfraredSceneLearn);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexRenameButton) {
+            infrared->app_state.edit_target = InfraredEditTargetButton;
+            infrared->app_state.edit_mode = InfraredEditModeRename;
+            scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexDeleteButton) {
+            infrared->app_state.edit_target = InfraredEditTargetButton;
+            infrared->app_state.edit_mode = InfraredEditModeDelete;
+            scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexRenameRemote) {
+            infrared->app_state.edit_target = InfraredEditTargetRemote;
+            infrared->app_state.edit_mode = InfraredEditModeRename;
+            scene_manager_next_scene(scene_manager, InfraredSceneEditRename);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexDeleteRemote) {
+            infrared->app_state.edit_target = InfraredEditTargetRemote;
+            infrared->app_state.edit_mode = InfraredEditModeDelete;
+            scene_manager_next_scene(scene_manager, InfraredSceneEditDelete);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_edit_on_exit(void* context) {
+    Infrared* infrared = context;
+    submenu_reset(infrared->submenu);
+}

+ 63 - 0
applications/infrared/scenes/infrared_scene_edit_button_select.c

@@ -0,0 +1,63 @@
+#include "../infrared_i.h"
+
+static void infrared_scene_edit_button_select_submenu_callback(void* context, uint32_t index) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
+}
+
+void infrared_scene_edit_button_select_on_enter(void* context) {
+    Infrared* infrared = context;
+    Submenu* submenu = infrared->submenu;
+    InfraredRemote* remote = infrared->remote;
+    InfraredAppState* app_state = &infrared->app_state;
+
+    const char* header = infrared->app_state.edit_mode == InfraredEditModeRename ?
+                             "Rename Button:" :
+                             "Delete Button:";
+    submenu_set_header(submenu, header);
+
+    const size_t button_count = infrared_remote_get_button_count(remote);
+    for(size_t i = 0; i < button_count; ++i) {
+        InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
+        submenu_add_item(
+            submenu,
+            infrared_remote_button_get_name(button),
+            i,
+            infrared_scene_edit_button_select_submenu_callback,
+            context);
+    }
+
+    if(button_count && app_state->current_button_index != InfraredButtonIndexNone) {
+        submenu_set_selected_item(submenu, app_state->current_button_index);
+        app_state->current_button_index = InfraredButtonIndexNone;
+    }
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
+}
+
+bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    InfraredAppState* app_state = &infrared->app_state;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        app_state->current_button_index = event.event;
+        const InfraredEditMode edit_mode = app_state->edit_mode;
+        if(edit_mode == InfraredEditModeRename) {
+            scene_manager_next_scene(scene_manager, InfraredSceneEditRename);
+        } else if(edit_mode == InfraredEditModeDelete) {
+            scene_manager_next_scene(scene_manager, InfraredSceneEditDelete);
+        } else {
+            furi_assert(0);
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void infrared_scene_edit_button_select_on_exit(void* context) {
+    Infrared* infrared = context;
+    submenu_reset(infrared->submenu);
+}

+ 112 - 0
applications/infrared/scenes/infrared_scene_edit_delete.c

@@ -0,0 +1,112 @@
+#include "../infrared_i.h"
+
+static void
+    infrared_scene_edit_delete_dialog_result_callback(DialogExResult result, void* context) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
+}
+
+void infrared_scene_edit_delete_on_enter(void* context) {
+    Infrared* infrared = context;
+    DialogEx* dialog_ex = infrared->dialog_ex;
+    InfraredRemote* remote = infrared->remote;
+
+    const InfraredEditTarget edit_target = infrared->app_state.edit_target;
+    if(edit_target == InfraredEditTargetButton) {
+        int32_t current_button_index = infrared->app_state.current_button_index;
+        furi_assert(current_button_index != InfraredButtonIndexNone);
+
+        dialog_ex_set_header(dialog_ex, "Delete button?", 64, 0, AlignCenter, AlignTop);
+        InfraredRemoteButton* current_button =
+            infrared_remote_get_button(remote, current_button_index);
+        InfraredSignal* signal = infrared_remote_button_get_signal(current_button);
+
+        if(infrared_signal_is_raw(signal)) {
+            const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
+            infrared_text_store_set(
+                infrared,
+                0,
+                "%s\nRAW\n%ld samples",
+                infrared_remote_button_get_name(current_button),
+                raw->timings_size);
+
+        } else {
+            const InfraredMessage* message = infrared_signal_get_message(signal);
+            infrared_text_store_set(
+                infrared,
+                0,
+                "%s\n%s\nA=0x%0*lX C=0x%0*lX",
+                infrared_remote_button_get_name(current_button),
+                infrared_get_protocol_name(message->protocol),
+                ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
+                message->address,
+                ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
+                message->command);
+        }
+
+    } else if(edit_target == InfraredEditTargetRemote) {
+        dialog_ex_set_header(dialog_ex, "Delete remote?", 64, 0, AlignCenter, AlignTop);
+        infrared_text_store_set(
+            infrared,
+            0,
+            "%s\n with %lu buttons",
+            infrared_remote_get_name(remote),
+            infrared_remote_get_button_count(remote));
+    } else {
+        furi_assert(0);
+    }
+
+    dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter);
+    dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
+    dialog_ex_set_left_button_text(dialog_ex, "Cancel");
+    dialog_ex_set_right_button_text(dialog_ex, "Delete");
+    dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback);
+    dialog_ex_set_context(dialog_ex, context);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
+}
+
+bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultLeft) {
+            scene_manager_previous_scene(scene_manager);
+            consumed = true;
+        } else if(event.event == DialogExResultRight) {
+            bool success = false;
+            InfraredRemote* remote = infrared->remote;
+            InfraredAppState* app_state = &infrared->app_state;
+            const InfraredEditTarget edit_target = app_state->edit_target;
+
+            if(edit_target == InfraredEditTargetButton) {
+                furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
+                success = infrared_remote_delete_button(remote, app_state->current_button_index);
+                app_state->current_button_index = InfraredButtonIndexNone;
+            } else if(edit_target == InfraredEditTargetRemote) {
+                success = infrared_remote_remove(remote);
+                app_state->current_button_index = InfraredButtonIndexNone;
+            } else {
+                furi_assert(0);
+            }
+
+            if(success) {
+                scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
+            } else {
+                const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
+                scene_manager_search_and_switch_to_previous_scene_one_of(
+                    scene_manager, possible_scenes, sizeof(possible_scenes) / sizeof(uint32_t));
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_edit_delete_on_exit(void* context) {
+    Infrared* infrared = context;
+    UNUSED(infrared);
+}

+ 46 - 0
applications/infrared/scenes/infrared_scene_edit_delete_done.c

@@ -0,0 +1,46 @@
+#include "../infrared_i.h"
+
+void infrared_scene_edit_delete_done_on_enter(void* context) {
+    Infrared* infrared = context;
+    Popup* popup = infrared->popup;
+
+    popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
+    popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
+
+    popup_set_callback(popup, infrared_popup_timeout_callback);
+    popup_set_context(popup, context);
+    popup_set_timeout(popup, 1500);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
+}
+
+bool infrared_scene_edit_delete_done_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypePopupTimeout) {
+            const InfraredEditTarget edit_target = infrared->app_state.edit_target;
+            if(edit_target == InfraredEditTargetButton) {
+                scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, InfraredSceneRemote);
+            } else if(edit_target == InfraredEditTargetRemote) {
+                const uint32_t possible_scenes[] = {InfraredSceneStart, InfraredSceneRemoteList};
+                scene_manager_search_and_switch_to_previous_scene_one_of(
+                    scene_manager, possible_scenes, sizeof(possible_scenes) / sizeof(uint32_t));
+            } else {
+                furi_assert(0);
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_edit_delete_done_on_exit(void* context) {
+    Infrared* infrared = context;
+    UNUSED(infrared);
+}

+ 107 - 0
applications/infrared/scenes/infrared_scene_edit_rename.c

@@ -0,0 +1,107 @@
+#include "../infrared_i.h"
+
+#include <string.h>
+#include <toolbox/path.h>
+
+void infrared_scene_edit_rename_on_enter(void* context) {
+    Infrared* infrared = context;
+    InfraredRemote* remote = infrared->remote;
+    TextInput* text_input = infrared->text_input;
+    size_t enter_name_length = 0;
+
+    const InfraredEditTarget edit_target = infrared->app_state.edit_target;
+    if(edit_target == InfraredEditTargetButton) {
+        text_input_set_header_text(text_input, "Name the button");
+
+        const int32_t current_button_index = infrared->app_state.current_button_index;
+        furi_assert(current_button_index != InfraredButtonIndexNone);
+
+        InfraredRemoteButton* current_button =
+            infrared_remote_get_button(remote, current_button_index);
+        enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH;
+        strncpy(
+            infrared->text_store[0],
+            infrared_remote_button_get_name(current_button),
+            enter_name_length);
+
+    } else if(edit_target == InfraredEditTargetRemote) {
+        text_input_set_header_text(text_input, "Name the remote");
+        enter_name_length = INFRARED_MAX_REMOTE_NAME_LENGTH;
+        strncpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length);
+
+        string_t folder_path;
+        string_init(folder_path);
+
+        if(string_end_with_str_p(infrared->file_path, INFRARED_APP_EXTENSION)) {
+            path_extract_dirname(string_get_cstr(infrared->file_path), folder_path);
+        }
+
+        ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+            string_get_cstr(folder_path),
+            INFRARED_APP_EXTENSION,
+            infrared_remote_get_name(remote));
+        text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+        string_clear(folder_path);
+    } else {
+        furi_assert(0);
+    }
+
+    text_input_set_result_callback(
+        text_input,
+        infrared_text_input_callback,
+        context,
+        infrared->text_store[0],
+        enter_name_length,
+        false);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
+}
+
+bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    InfraredRemote* remote = infrared->remote;
+    SceneManager* scene_manager = infrared->scene_manager;
+    InfraredAppState* app_state = &infrared->app_state;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypeTextEditDone) {
+            bool success = false;
+            const InfraredEditTarget edit_target = app_state->edit_target;
+            if(edit_target == InfraredEditTargetButton) {
+                const int32_t current_button_index = app_state->current_button_index;
+                furi_assert(current_button_index != InfraredButtonIndexNone);
+                success = infrared_remote_rename_button(
+                    remote, infrared->text_store[0], current_button_index);
+                app_state->current_button_index = InfraredButtonIndexNone;
+            } else if(edit_target == InfraredEditTargetRemote) {
+                success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
+            } else {
+                furi_assert(0);
+            }
+
+            if(success) {
+                scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone);
+            } else {
+                scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, InfraredSceneRemoteList);
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_edit_rename_on_exit(void* context) {
+    Infrared* infrared = context;
+    TextInput* text_input = infrared->text_input;
+
+    void* validator_context = text_input_get_validator_callback_context(text_input);
+    text_input_set_validator(text_input, NULL, NULL);
+
+    if(validator_context) {
+        validator_is_file_free((ValidatorIsFile*)validator_context);
+    }
+}

+ 35 - 0
applications/infrared/scenes/infrared_scene_edit_rename_done.c

@@ -0,0 +1,35 @@
+#include "../infrared_i.h"
+
+void infrared_scene_edit_rename_done_on_enter(void* context) {
+    Infrared* infrared = context;
+    Popup* popup = infrared->popup;
+
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
+
+    popup_set_callback(popup, infrared_popup_timeout_callback);
+    popup_set_context(popup, context);
+    popup_set_timeout(popup, 1500);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
+}
+
+bool infrared_scene_edit_rename_done_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypePopupTimeout) {
+            scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_edit_rename_done_on_exit(void* context) {
+    Infrared* infrared = context;
+    UNUSED(infrared);
+}

+ 46 - 0
applications/infrared/scenes/infrared_scene_learn.c

@@ -0,0 +1,46 @@
+#include "../infrared_i.h"
+
+void infrared_scene_learn_on_enter(void* context) {
+    Infrared* infrared = context;
+    Popup* popup = infrared->popup;
+    InfraredWorker* worker = infrared->worker;
+
+    infrared_worker_rx_set_received_signal_callback(
+        worker, infrared_signal_received_callback, context);
+    infrared_worker_rx_start(worker);
+
+    popup_set_icon(popup, 0, 32, &I_InfraredLearnShort_128x31);
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignCenter);
+    popup_set_text(
+        popup, "Point the remote at IR port\nand push the button", 5, 10, AlignLeft, AlignCenter);
+    popup_set_callback(popup, NULL);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
+}
+
+bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeTick) {
+        infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkRead);
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypeSignalReceived) {
+            infrared_worker_rx_set_received_signal_callback(infrared->worker, NULL, NULL);
+            infrared_play_notification_message(infrared, InfraredNotificationMessageSuccess);
+            scene_manager_next_scene(infrared->scene_manager, InfraredSceneLearnSuccess);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_learn_on_exit(void* context) {
+    Infrared* infrared = context;
+    Popup* popup = infrared->popup;
+    infrared_worker_rx_stop(infrared->worker);
+    popup_set_icon(popup, 0, 0, NULL);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignCenter);
+}

+ 44 - 0
applications/infrared/scenes/infrared_scene_learn_done.c

@@ -0,0 +1,44 @@
+#include "../infrared_i.h"
+
+#include <dolphin/dolphin.h>
+
+void infrared_scene_learn_done_on_enter(void* context) {
+    Infrared* infrared = context;
+    Popup* popup = infrared->popup;
+
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    DOLPHIN_DEED(DolphinDeedIrSave);
+
+    if(infrared->app_state.is_learning_new_remote) {
+        popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
+    } else {
+        popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
+    }
+
+    popup_set_callback(popup, infrared_popup_timeout_callback);
+    popup_set_context(popup, context);
+    popup_set_timeout(popup, 1500);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
+}
+
+bool infrared_scene_learn_done_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypePopupTimeout) {
+            scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_learn_done_on_exit(void* context) {
+    Infrared* infrared = context;
+    infrared->app_state.is_learning_new_remote = false;
+    popup_set_header(infrared->popup, NULL, 0, 0, AlignLeft, AlignTop);
+}

+ 66 - 0
applications/infrared/scenes/infrared_scene_learn_enter_name.c

@@ -0,0 +1,66 @@
+#include "../infrared_i.h"
+
+void infrared_scene_learn_enter_name_on_enter(void* context) {
+    Infrared* infrared = context;
+    TextInput* text_input = infrared->text_input;
+    InfraredSignal* signal = infrared->received_signal;
+
+    if(infrared_signal_is_raw(signal)) {
+        InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
+        infrared_text_store_set(infrared, 0, "RAW_%d", raw->timings_size);
+    } else {
+        InfraredMessage* message = infrared_signal_get_message(signal);
+        infrared_text_store_set(
+            infrared,
+            0,
+            "%.4s_%0*lX",
+            infrared_get_protocol_name(message->protocol),
+            ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
+            message->command);
+    }
+
+    text_input_set_header_text(text_input, "Name the button");
+    text_input_set_result_callback(
+        text_input,
+        infrared_text_input_callback,
+        context,
+        infrared->text_store[0],
+        INFRARED_MAX_BUTTON_NAME_LENGTH,
+        true);
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
+}
+
+bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    InfraredSignal* signal = infrared->received_signal;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InfraredCustomEventTypeTextEditDone) {
+            bool success = false;
+            if(infrared->app_state.is_learning_new_remote) {
+                success =
+                    infrared_add_remote_with_button(infrared, infrared->text_store[0], signal);
+            } else {
+                success =
+                    infrared_remote_add_button(infrared->remote, infrared->text_store[0], signal);
+            }
+
+            if(success) {
+                scene_manager_next_scene(scene_manager, InfraredSceneLearnDone);
+            } else {
+                scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, InfraredSceneRemoteList);
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_learn_enter_name_on_exit(void* context) {
+    Infrared* infrared = context;
+    UNUSED(infrared);
+}

+ 131 - 0
applications/infrared/scenes/infrared_scene_learn_success.c

@@ -0,0 +1,131 @@
+#include "../infrared_i.h"
+
+#include <dolphin/dolphin.h>
+
+typedef enum {
+    InfraredSceneLearnSuccessStateIdle = 0,
+    InfraredSceneLearnSuccessStateSending = 1,
+} InfraredSceneLearnSuccessState;
+
+static void
+    infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
+}
+
+void infrared_scene_learn_success_on_enter(void* context) {
+    Infrared* infrared = context;
+    DialogEx* dialog_ex = infrared->dialog_ex;
+    InfraredSignal* signal = infrared->received_signal;
+
+    DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
+    infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
+
+    infrared_worker_tx_set_get_signal_callback(
+        infrared->worker, infrared_worker_tx_get_signal_steady_callback, context);
+    infrared_worker_tx_set_signal_sent_callback(
+        infrared->worker, infrared_signal_sent_callback, context);
+
+    if(infrared_signal_is_raw(signal)) {
+        InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
+        dialog_ex_set_header(dialog_ex, "Unknown", 95, 10, AlignCenter, AlignCenter);
+        infrared_text_store_set(infrared, 0, "%d samples", raw->timings_size);
+        dialog_ex_set_text(dialog_ex, infrared->text_store[0], 75, 23, AlignLeft, AlignTop);
+
+    } else {
+        InfraredMessage* message = infrared_signal_get_message(signal);
+        uint8_t addr_digits =
+            ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4);
+        uint8_t cmd_digits =
+            ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4);
+        uint8_t max_digits = MAX(addr_digits, cmd_digits);
+        max_digits = MIN(max_digits, 7);
+        size_t label_x_offset = 63 + (7 - max_digits) * 3;
+
+        infrared_text_store_set(infrared, 0, "%s", infrared_get_protocol_name(message->protocol));
+        infrared_text_store_set(
+            infrared,
+            1,
+            "A: 0x%0*lX\nC: 0x%0*lX\n",
+            addr_digits,
+            message->address,
+            cmd_digits,
+            message->command);
+
+        dialog_ex_set_header(dialog_ex, infrared->text_store[0], 95, 7, AlignCenter, AlignCenter);
+        dialog_ex_set_text(
+            dialog_ex, infrared->text_store[1], label_x_offset, 34, AlignLeft, AlignCenter);
+    }
+
+    dialog_ex_set_left_button_text(dialog_ex, "Retry");
+    dialog_ex_set_right_button_text(dialog_ex, "Save");
+    dialog_ex_set_center_button_text(dialog_ex, "Send");
+    dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
+    dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_success_dialog_result_callback);
+    dialog_ex_set_context(dialog_ex, context);
+    dialog_ex_enable_extended_events(dialog_ex);
+
+    scene_manager_set_scene_state(
+        infrared->scene_manager, InfraredSceneLearnSuccess, InfraredSceneLearnSuccessStateIdle);
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
+}
+
+bool infrared_scene_learn_success_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    uint32_t scene_state = scene_manager_get_scene_state(scene_manager, InfraredSceneLearnSuccess);
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeTick) {
+        if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+            infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOn);
+        }
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeBack) {
+        if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+            scene_manager_next_scene(scene_manager, InfraredSceneAskBack);
+        }
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultLeft) {
+            if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+                scene_manager_search_and_switch_to_previous_scene(
+                    scene_manager, InfraredSceneLearn);
+            }
+            consumed = true;
+        } else if(event.event == DialogExResultRight) {
+            if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+                scene_manager_next_scene(scene_manager, InfraredSceneLearnEnterName);
+            }
+            consumed = true;
+        } else if(event.event == DialogExPressCenter) {
+            if(scene_state == InfraredSceneLearnSuccessStateIdle) {
+                scene_manager_set_scene_state(
+                    scene_manager,
+                    InfraredSceneLearnSuccess,
+                    InfraredSceneLearnSuccessStateSending);
+                infrared_tx_start_received(infrared);
+            }
+            consumed = true;
+        } else if(event.event == DialogExReleaseCenter) {
+            if(scene_state == InfraredSceneLearnSuccessStateSending) {
+                scene_manager_set_scene_state(
+                    scene_manager, InfraredSceneLearnSuccess, InfraredSceneLearnSuccessStateIdle);
+                infrared_tx_stop(infrared);
+                infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_learn_success_on_exit(void* context) {
+    Infrared* infrared = context;
+    InfraredWorker* worker = infrared->worker;
+    dialog_ex_reset(infrared->dialog_ex);
+    infrared_play_notification_message(infrared, InfraredNotificationMessageGreenOff);
+    infrared_worker_tx_set_get_signal_callback(worker, NULL, NULL);
+    infrared_worker_tx_set_signal_sent_callback(worker, NULL, NULL);
+}

+ 119 - 0
applications/infrared/scenes/infrared_scene_remote.c

@@ -0,0 +1,119 @@
+#include "../infrared_i.h"
+
+typedef enum {
+    ButtonIndexPlus = -2,
+    ButtonIndexEdit = -1,
+    ButtonIndexNA = 0,
+} ButtonIndex;
+
+static void
+    infrared_scene_remote_button_menu_callback(void* context, int32_t index, InputType type) {
+    Infrared* infrared = context;
+
+    uint16_t custom_type;
+    if(type == InputTypePress) {
+        custom_type = InfraredCustomEventTypeTransmitStarted;
+    } else if(type == InputTypeRelease) {
+        custom_type = InfraredCustomEventTypeTransmitStopped;
+    } else if(type == InputTypeShort) {
+        custom_type = InfraredCustomEventTypeMenuSelected;
+    } else {
+        furi_crash("Unexpected input type");
+    }
+
+    view_dispatcher_send_custom_event(
+        infrared->view_dispatcher, infrared_custom_event_pack(custom_type, index));
+}
+
+void infrared_scene_remote_on_enter(void* context) {
+    Infrared* infrared = context;
+    InfraredRemote* remote = infrared->remote;
+    ButtonMenu* button_menu = infrared->button_menu;
+    SceneManager* scene_manager = infrared->scene_manager;
+
+    infrared_worker_tx_set_get_signal_callback(
+        infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
+    infrared_worker_tx_set_signal_sent_callback(
+        infrared->worker, infrared_signal_sent_callback, infrared);
+
+    size_t button_count = infrared_remote_get_button_count(remote);
+    for(size_t i = 0; i < button_count; ++i) {
+        InfraredRemoteButton* button = infrared_remote_get_button(remote, i);
+        button_menu_add_item(
+            button_menu,
+            infrared_remote_button_get_name(button),
+            i,
+            infrared_scene_remote_button_menu_callback,
+            ButtonMenuItemTypeCommon,
+            context);
+    }
+
+    button_menu_add_item(
+        button_menu,
+        "+",
+        ButtonIndexPlus,
+        infrared_scene_remote_button_menu_callback,
+        ButtonMenuItemTypeControl,
+        context);
+    button_menu_add_item(
+        button_menu,
+        "Edit",
+        ButtonIndexEdit,
+        infrared_scene_remote_button_menu_callback,
+        ButtonMenuItemTypeControl,
+        context);
+
+    button_menu_set_header(button_menu, infrared_remote_get_name(remote));
+    const int16_t button_index =
+        (signed)scene_manager_get_scene_state(scene_manager, InfraredSceneRemote);
+    button_menu_set_selected_item(button_menu, button_index);
+    scene_manager_set_scene_state(scene_manager, InfraredSceneRemote, ButtonIndexNA);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewButtonMenu);
+}
+
+bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
+        scene_manager_search_and_switch_to_previous_scene_one_of(
+            scene_manager, possible_scenes, sizeof(possible_scenes) / sizeof(uint32_t));
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        const uint16_t custom_type = infrared_custom_event_get_type(event.event);
+        const int16_t button_index = infrared_custom_event_get_value(event.event);
+
+        if(custom_type == InfraredCustomEventTypeTransmitStarted) {
+            furi_assert(button_index >= 0);
+            infrared_tx_start_button_index(infrared, button_index);
+            consumed = true;
+        } else if(custom_type == InfraredCustomEventTypeTransmitStopped) {
+            infrared_tx_stop(infrared);
+            consumed = true;
+        } else if(custom_type == InfraredCustomEventTypeMenuSelected) {
+            furi_assert(button_index < 0);
+            scene_manager_set_scene_state(
+                scene_manager, InfraredSceneRemote, (unsigned)button_index);
+            if(button_index == ButtonIndexPlus) {
+                infrared->app_state.is_learning_new_remote = false;
+                scene_manager_next_scene(scene_manager, InfraredSceneLearn);
+                consumed = true;
+            } else if(button_index == ButtonIndexEdit) {
+                scene_manager_next_scene(scene_manager, InfraredSceneEdit);
+                consumed = true;
+            }
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_remote_on_exit(void* context) {
+    Infrared* infrared = context;
+    infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
+    infrared_worker_tx_set_signal_sent_callback(infrared->worker, NULL, NULL);
+    button_menu_reset(infrared->button_menu);
+}

+ 44 - 0
applications/infrared/scenes/infrared_scene_remote_list.c

@@ -0,0 +1,44 @@
+#include "../infrared_i.h"
+
+void infrared_scene_remote_list_on_enter(void* context) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
+
+    string_set_str(infrared->file_path, INFRARED_APP_FOLDER);
+    bool success = dialog_file_browser_show(
+        infrared->dialogs,
+        infrared->file_path,
+        infrared->file_path,
+        INFRARED_APP_EXTENSION,
+        true,
+        &I_ir_10px,
+        true);
+
+    if(success) {
+        view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
+        view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack);
+
+        infrared_show_loading_popup(infrared, true);
+        success = infrared_remote_load(infrared->remote, infrared->file_path);
+        infrared_show_loading_popup(infrared, false);
+    }
+
+    if(success) {
+        scene_manager_next_scene(scene_manager, InfraredSceneRemote);
+    } else {
+        scene_manager_previous_scene(scene_manager);
+    }
+}
+
+bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = false;
+
+    return consumed;
+}
+
+void infrared_scene_remote_list_on_exit(void* context) {
+    UNUSED(context);
+}

+ 83 - 0
applications/infrared/scenes/infrared_scene_start.c

@@ -0,0 +1,83 @@
+#include "../infrared_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexUniversalRemotes,
+    SubmenuIndexLearnNewRemote,
+    SubmenuIndexSavedRemotes,
+    SubmenuIndexDebug
+};
+
+static void infrared_scene_start_submenu_callback(void* context, uint32_t index) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
+}
+
+void infrared_scene_start_on_enter(void* context) {
+    Infrared* infrared = context;
+    Submenu* submenu = infrared->submenu;
+    SceneManager* scene_manager = infrared->scene_manager;
+
+    submenu_add_item(
+        submenu,
+        "Universal Remotes",
+        SubmenuIndexUniversalRemotes,
+        infrared_scene_start_submenu_callback,
+        infrared);
+    submenu_add_item(
+        submenu,
+        "Learn New Remote",
+        SubmenuIndexLearnNewRemote,
+        infrared_scene_start_submenu_callback,
+        infrared);
+    submenu_add_item(
+        submenu,
+        "Saved Remotes",
+        SubmenuIndexSavedRemotes,
+        infrared_scene_start_submenu_callback,
+        infrared);
+
+    if(infrared->app_state.is_debug_enabled) {
+        submenu_add_item(
+            submenu, "Debug", SubmenuIndexDebug, infrared_scene_start_submenu_callback, infrared);
+    }
+
+    const uint32_t submenu_index =
+        scene_manager_get_scene_state(scene_manager, InfraredSceneStart);
+    submenu_set_selected_item(submenu, submenu_index);
+    scene_manager_set_scene_state(scene_manager, InfraredSceneStart, SubmenuIndexUniversalRemotes);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
+}
+
+bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        const uint32_t submenu_index = event.event;
+        scene_manager_set_scene_state(scene_manager, InfraredSceneStart, submenu_index);
+        if(submenu_index == SubmenuIndexUniversalRemotes) {
+            scene_manager_next_scene(scene_manager, InfraredSceneUniversal);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexLearnNewRemote) {
+            infrared->app_state.is_learning_new_remote = true;
+            scene_manager_next_scene(scene_manager, InfraredSceneLearn);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexSavedRemotes) {
+            scene_manager_next_scene(scene_manager, InfraredSceneRemoteList);
+            consumed = true;
+        } else if(submenu_index == SubmenuIndexDebug) {
+            scene_manager_next_scene(scene_manager, InfraredSceneDebug);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_start_on_exit(void* context) {
+    Infrared* infrared = context;
+    submenu_reset(infrared->submenu);
+}

+ 53 - 0
applications/infrared/scenes/infrared_scene_universal.c

@@ -0,0 +1,53 @@
+#include "../infrared_i.h"
+
+typedef enum {
+    SubmenuIndexUniversalTV,
+    SubmenuIndexUniversalAudio,
+    SubmenuIndexUniversalAirConditioner,
+} SubmenuIndex;
+
+static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) {
+    Infrared* infrared = context;
+    view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
+}
+
+void infrared_scene_universal_on_enter(void* context) {
+    Infrared* infrared = context;
+    Submenu* submenu = infrared->submenu;
+
+    submenu_add_item(
+        submenu,
+        "TVs",
+        SubmenuIndexUniversalTV,
+        infrared_scene_universal_submenu_callback,
+        context);
+    submenu_set_selected_item(submenu, 0);
+
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
+}
+
+bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    SceneManager* scene_manager = infrared->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexUniversalTV) {
+            scene_manager_next_scene(scene_manager, InfraredSceneUniversalTV);
+            consumed = true;
+        } else if(event.event == SubmenuIndexUniversalAudio) {
+            //TODO Implement Audio universal remote
+            consumed = true;
+        } else if(event.event == SubmenuIndexUniversalAirConditioner) {
+            //TODO Implement A/C universal remote
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void infrared_scene_universal_on_exit(void* context) {
+    Infrared* infrared = context;
+    submenu_reset(infrared->submenu);
+}

+ 111 - 0
applications/infrared/scenes/infrared_scene_universal_tv.c

@@ -0,0 +1,111 @@
+#include "../infrared_i.h"
+
+#include "common/infrared_scene_universal_common.h"
+
+void infrared_scene_universal_tv_on_enter(void* context) {
+    infrared_scene_universal_common_on_enter(context);
+
+    Infrared* infrared = context;
+    ButtonPanel* button_panel = infrared->button_panel;
+    InfraredBruteForce* brute_force = infrared->brute_force;
+
+    infrared_brute_force_set_db_filename(brute_force, "/ext/infrared/assets/tv.ir");
+
+    button_panel_reserve(button_panel, 2, 3);
+    uint32_t i = 0;
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        0,
+        3,
+        19,
+        &I_Power_25x27,
+        &I_Power_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "POWER");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        0,
+        36,
+        19,
+        &I_Mute_25x27,
+        &I_Mute_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "MUTE");
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        1,
+        3,
+        66,
+        &I_Vol_up_25x27,
+        &I_Vol_up_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "VOL+");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        1,
+        36,
+        66,
+        &I_Up_25x27,
+        &I_Up_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "CH+");
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        2,
+        3,
+        98,
+        &I_Vol_down_25x27,
+        &I_Vol_down_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "VOL-");
+    button_panel_add_item(
+        button_panel,
+        i,
+        1,
+        2,
+        36,
+        98,
+        &I_Down_25x27,
+        &I_Down_hvr_25x27,
+        infrared_scene_universal_common_item_callback,
+        context);
+    infrared_brute_force_add_record(brute_force, i++, "CH-");
+
+    button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote");
+    button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol");
+    button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch");
+
+    view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
+
+    infrared_show_loading_popup(infrared, true);
+    bool success = infrared_brute_force_calculate_messages(brute_force);
+    infrared_show_loading_popup(infrared, false);
+
+    if(!success) {
+        scene_manager_previous_scene(infrared->scene_manager);
+    }
+}
+
+bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) {
+    return infrared_scene_universal_common_on_event(context, event);
+}
+
+void infrared_scene_universal_tv_on_exit(void* context) {
+    infrared_scene_universal_common_on_exit(context);
+}

+ 59 - 0
applications/infrared/views/infrared_debug_view.c

@@ -0,0 +1,59 @@
+#include "infrared_debug_view.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gui/canvas.h>
+#include <gui/elements.h>
+
+#define INFRARED_DEBUG_TEXT_LENGTH 64
+
+struct InfraredDebugView {
+    View* view;
+};
+
+typedef struct {
+    char text[INFRARED_DEBUG_TEXT_LENGTH];
+} InfraredDebugViewModel;
+
+static void infrared_debug_view_draw_callback(Canvas* canvas, void* model) {
+    InfraredDebugViewModel* debug_view_model = model;
+
+    canvas_clear(canvas);
+    canvas_set_font(canvas, FontPrimary);
+    elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "INFRARED monitor\n");
+    canvas_set_font(canvas, FontKeyboard);
+
+    if(strlen(debug_view_model->text)) {
+        elements_multiline_text_aligned(
+            canvas, 64, 43, AlignCenter, AlignCenter, debug_view_model->text);
+    }
+}
+
+InfraredDebugView* infrared_debug_view_alloc() {
+    InfraredDebugView* debug_view = malloc(sizeof(InfraredDebugView));
+    debug_view->view = view_alloc();
+    view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(InfraredDebugViewModel));
+    view_set_draw_callback(debug_view->view, infrared_debug_view_draw_callback);
+    view_set_context(debug_view->view, debug_view);
+    return debug_view;
+}
+void infrared_debug_view_free(InfraredDebugView* debug_view) {
+    view_free(debug_view->view);
+    free(debug_view);
+}
+
+View* infrared_debug_view_get_view(InfraredDebugView* debug_view) {
+    return debug_view->view;
+}
+
+void infrared_debug_view_set_text(InfraredDebugView* debug_view, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+
+    InfraredDebugViewModel* model = view_get_model(debug_view->view);
+    vsnprintf(model->text, INFRARED_DEBUG_TEXT_LENGTH, fmt, args);
+    view_commit_model(debug_view->view, true);
+
+    va_end(args);
+}

+ 11 - 0
applications/infrared/views/infrared_debug_view.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#include <gui/view.h>
+
+typedef struct InfraredDebugView InfraredDebugView;
+
+InfraredDebugView* infrared_debug_view_alloc();
+void infrared_debug_view_free(InfraredDebugView* debug_view);
+
+View* infrared_debug_view_get_view(InfraredDebugView* debug_view);
+void infrared_debug_view_set_text(InfraredDebugView* debug_view, const char* fmt, ...);

+ 0 - 0
applications/infrared/view/infrared_progress_view.c → applications/infrared/views/infrared_progress_view.c


+ 0 - 0
applications/infrared/view/infrared_progress_view.h → applications/infrared/views/infrared_progress_view.h


+ 0 - 140
applications/infrared_monitor/infrared_monitor.c

@@ -1,140 +0,0 @@
-#include <gui/canvas.h>
-#include <input/input.h>
-#include <infrared.h>
-#include <infrared_worker.h>
-#include <stdio.h>
-#include <furi.h>
-#include <furi_hal_infrared.h>
-#include <furi_hal.h>
-#include <gui/view_port.h>
-#include <gui/gui.h>
-#include <gui/elements.h>
-
-#define INFRARED_TIMINGS_SIZE 700
-
-typedef struct {
-    uint32_t timing_cnt;
-    struct {
-        uint8_t level;
-        uint32_t duration;
-    } timing[INFRARED_TIMINGS_SIZE];
-} InfraredDelaysArray;
-
-typedef struct {
-    char display_text[64];
-    osMessageQueueId_t event_queue;
-    InfraredDelaysArray delays;
-    InfraredWorker* worker;
-    ViewPort* view_port;
-} InfraredMonitor;
-
-void infrared_monitor_input_callback(InputEvent* input_event, void* ctx) {
-    furi_assert(ctx);
-    InfraredMonitor* infrared_monitor = (InfraredMonitor*)ctx;
-
-    if((input_event->type == InputTypeShort) && (input_event->key == InputKeyBack)) {
-        osMessageQueuePut(infrared_monitor->event_queue, input_event, 0, 0);
-    }
-}
-
-static void infrared_monitor_draw_callback(Canvas* canvas, void* ctx) {
-    furi_assert(canvas);
-    furi_assert(ctx);
-    InfraredMonitor* infrared_monitor = (InfraredMonitor*)ctx;
-
-    canvas_clear(canvas);
-    canvas_set_font(canvas, FontPrimary);
-    elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "INFRARED monitor\n");
-    canvas_set_font(canvas, FontKeyboard);
-    if(strlen(infrared_monitor->display_text)) {
-        elements_multiline_text_aligned(
-            canvas, 64, 43, AlignCenter, AlignCenter, infrared_monitor->display_text);
-    }
-}
-
-static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
-    furi_assert(context);
-    furi_assert(received_signal);
-    InfraredMonitor* infrared_monitor = context;
-
-    if(infrared_worker_signal_is_decoded(received_signal)) {
-        const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
-        snprintf(
-            infrared_monitor->display_text,
-            sizeof(infrared_monitor->display_text),
-            "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n",
-            infrared_get_protocol_name(message->protocol),
-            ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
-            message->address,
-            ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
-            message->command,
-            message->repeat ? " R" : "");
-        view_port_update(infrared_monitor->view_port);
-        printf(
-            "== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n",
-            infrared_get_protocol_name(message->protocol),
-            ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
-            message->address,
-            ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
-            message->command,
-            message->repeat ? " R" : "");
-    } else {
-        const uint32_t* timings;
-        size_t timings_cnt;
-        infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
-        snprintf(
-            infrared_monitor->display_text,
-            sizeof(infrared_monitor->display_text),
-            "RAW\n%d samples\n",
-            timings_cnt);
-        view_port_update(infrared_monitor->view_port);
-        printf("RAW, %d samples:\r\n", timings_cnt);
-        for(size_t i = 0; i < timings_cnt; ++i) {
-            printf("%lu ", timings[i]);
-        }
-        printf("\r\n");
-    }
-}
-
-int32_t infrared_monitor_app(void* p) {
-    (void)p;
-
-    InfraredMonitor* infrared_monitor = malloc(sizeof(InfraredMonitor));
-    infrared_monitor->display_text[0] = 0;
-    infrared_monitor->event_queue = osMessageQueueNew(1, sizeof(InputEvent), NULL);
-    infrared_monitor->view_port = view_port_alloc();
-    Gui* gui = furi_record_open("gui");
-
-    view_port_draw_callback_set(
-        infrared_monitor->view_port, infrared_monitor_draw_callback, infrared_monitor);
-    view_port_input_callback_set(
-        infrared_monitor->view_port, infrared_monitor_input_callback, infrared_monitor);
-
-    gui_add_view_port(gui, infrared_monitor->view_port, GuiLayerFullscreen);
-
-    infrared_monitor->worker = infrared_worker_alloc();
-    infrared_worker_rx_start(infrared_monitor->worker);
-    infrared_worker_rx_set_received_signal_callback(
-        infrared_monitor->worker, signal_received_callback, infrared_monitor);
-    infrared_worker_rx_enable_blink_on_receiving(infrared_monitor->worker, true);
-
-    while(1) {
-        InputEvent event;
-        if(osOK == osMessageQueueGet(infrared_monitor->event_queue, &event, NULL, 50)) {
-            if((event.type == InputTypeShort) && (event.key == InputKeyBack)) {
-                break;
-            }
-        }
-    }
-
-    infrared_worker_rx_stop(infrared_monitor->worker);
-    infrared_worker_free(infrared_monitor->worker);
-    osMessageQueueDelete(infrared_monitor->event_queue);
-    view_port_enabled_set(infrared_monitor->view_port, false);
-    gui_remove_view_port(gui, infrared_monitor->view_port);
-    view_port_free(infrared_monitor->view_port);
-    furi_record_close("gui");
-    free(infrared_monitor);
-
-    return 0;
-}