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

Moved to new repo, updated for compatibility to 0.95.0-RC

David Lee пре 2 година
комит
83d453ad36
72 измењених фајлова са 4136 додато и 0 уклоњено
  1. 191 0
      .clang-format
  2. 45 0
      README.md
  3. 22 0
      application.fam
  4. 22 0
      docs/README.md
  5. 6 0
      docs/changelog.md
  6. 76 0
      helpers/xremote_custom_event.h
  7. 36 0
      helpers/xremote_haptic.c
  8. 10 0
      helpers/xremote_haptic.h
  9. 36 0
      helpers/xremote_led.c
  10. 8 0
      helpers/xremote_led.h
  11. 26 0
      helpers/xremote_speaker.c
  12. 8 0
      helpers/xremote_speaker.h
  13. 120 0
      helpers/xremote_storage.c
  14. 16 0
      helpers/xremote_storage.h
  15. BIN
      icons/ButtonDown_10x5.png
  16. BIN
      icons/ButtonUp_10x5.png
  17. BIN
      icons/ir_10px.png
  18. BIN
      icons/ir_transmit_128x64.png
  19. BIN
      icons/pause_128x64.png
  20. BIN
      icons/sg_10px.png
  21. BIN
      icons/sg_transmit_128x64.png
  22. BIN
      icons/xr_10px.png
  23. BIN
      icons/xremote_10px.png
  24. 265 0
      models/cross/xremote_remote.c
  25. 28 0
      models/cross/xremote_remote.h
  26. 241 0
      models/cross/xremote_remote_item.c
  27. 34 0
      models/cross/xremote_remote_item.h
  28. 90 0
      models/infrared/xremote_ir_remote.c
  29. 22 0
      models/infrared/xremote_ir_remote.h
  30. 33 0
      models/infrared/xremote_ir_remote_button.c
  31. 13 0
      models/infrared/xremote_ir_remote_button.h
  32. 218 0
      models/infrared/xremote_ir_signal.c
  33. 38 0
      models/infrared/xremote_ir_signal.h
  34. 57 0
      models/subghz/subghz_i.h
  35. 79 0
      models/subghz/subghz_types.h
  36. 218 0
      models/subghz/xremote_sg_remote.c
  37. 21 0
      models/subghz/xremote_sg_remote.h
  38. 30 0
      scenes/xremote_scene.c
  39. 29 0
      scenes/xremote_scene.h
  40. 17 0
      scenes/xremote_scene_config.h
  41. 130 0
      scenes/xremote_scene_create.c
  42. 97 0
      scenes/xremote_scene_create_add.c
  43. 50 0
      scenes/xremote_scene_edit_item.c
  44. 54 0
      scenes/xremote_scene_infoscreen.c
  45. 46 0
      scenes/xremote_scene_ir_list.c
  46. 78 0
      scenes/xremote_scene_ir_remote.c
  47. 70 0
      scenes/xremote_scene_menu.c
  48. 54 0
      scenes/xremote_scene_pause_set.c
  49. 74 0
      scenes/xremote_scene_save_remote.c
  50. 45 0
      scenes/xremote_scene_save_remote_item.c
  51. 147 0
      scenes/xremote_scene_settings.c
  52. 48 0
      scenes/xremote_scene_sg_list.c
  53. 154 0
      scenes/xremote_scene_transmit.c
  54. 39 0
      scenes/xremote_scene_wip.c
  55. 44 0
      scenes/xremote_scene_xr_list.c
  56. 43 0
      scenes/xremote_scene_xr_list_edit.c
  57. 51 0
      scenes/xremote_scene_xr_list_edit_item.c
  58. BIN
      screenshots/xremote_1.png
  59. BIN
      screenshots/xremote_2.png
  60. BIN
      screenshots/xremote_3.png
  61. BIN
      screenshots/xremote_4.png
  62. BIN
      screenshots/xremote_5.png
  63. BIN
      screenshots/xremote_6.png
  64. 134 0
      views/xremote_infoscreen.c
  65. 20 0
      views/xremote_infoscreen.h
  66. 136 0
      views/xremote_pause_set.c
  67. 23 0
      views/xremote_pause_set.h
  68. 151 0
      views/xremote_transmit.c
  69. 24 0
      views/xremote_transmit.h
  70. 170 0
      xremote.c
  71. 81 0
      xremote.h
  72. 118 0
      xremote_i.h

+ 191 - 0
.clang-format

@@ -0,0 +1,191 @@
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit:     99
+CommentPragmas:  '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle:    ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires:  false
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth:   -1
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: false
+  AfterForeachMacros: false
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   false
+  AfterOverloadedOperator: false
+  BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard:        c++03
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+# Flipper Zero Cross Remote
+
+## Current State
+- Infrared working
+- Pause working
+- SubGhz in Development
+- Edit/Rename/Delete features in development
+
+## What this is?
+This app combines commands used in IR and SubGhz into playlists that can be run with one click
+<br><br>
+
+### What good is this?
+Imagine you want to sit down and watch a movie after a long days work. <br>
+Your probably do something like the following<br>
+- Turn on your TV with the IR TV remote<br>
+- Turn on your Bluray player with the Blueray IR remote<br>
+- Turn on your surround sound with the speaker IR remote<br>
+- Turn on your ceiling fan using a subGhz remote<br>
+- Turn on your AC using another remote<br>
+- etc<br>
+<br>
+Wouldn't it be nicer to simply click one button and let everything happen? This is what this app wants to do. <br>
+
+### Features
+- Select commands from saved subGhz transmissions
+- Select commands registered with the IR App (choose from the created remote buttons)<br>
+- Chain these commands using an easy UI<br>
+- Save chained commands to a file<br>
+- Add pauses, becaue target systems are not always fast enough for multiple commands<br>
+- Run file containing chained IR & SubGhz commands<br>
+
+## How to install on Flipper Zero
+- If you do not have one, download a firmware<br>
+- Plug your Flipper Zero in via USB. <br>
+- Copy the contents of this folder into the applications_user folder of your firmware. <br> 
+
+Then run the command: 
+ ```
+.\fbt launch_app APPSRC=applications_user/xremote
+ ```
+The application will be compiled and copied onto your device. 
+
+## Licensing
+This code is open-source and may be used for whatever you want to do with it. 

+ 22 - 0
application.fam

@@ -0,0 +1,22 @@
+App(
+    appid="xremote",
+    name="Cross Remote",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="xremote_app",
+    cdefines=["APP_XREMOTE"],
+    requires=[
+        "gui",
+        "storage",
+        "dialogs",
+    ],
+    stack_size=3 * 1024,
+    order=10,
+    fap_libs=["assets"],
+    fap_icon="icons/xremote_10px.png",
+    fap_icon_assets="icons",
+    fap_version="0.8",
+    fap_category="Infrared",
+    fap_author="Leedave",
+    fap_description="One-Click, sends multiple commands",
+    fap_weburl="https://github.com/leedave/Leeds-Flipper-Zero-Applications"
+)

+ 22 - 0
docs/README.md

@@ -0,0 +1,22 @@
+## Cross Remote
+
+This app combines your IR commands into a playlist that can be run with one click. Pauses can be set inbetween, as IR devices typically are not so fast. 
+
+## Features
+- Read out commands you recorded in the IR app
+- Combine commands to a chain/playlist 
+- Add pauses inbetween commands 
+- Save your command chain / playlist to flipper
+- Disable LED effects if not wanted
+
+## In Development
+- Ability to also add SubGhz commands 
+
+## What good is this?
+
+Example what you command chain (playlist) could do with one click
+- Turn on your TV with the IR TV remote
+- Turn on your Bluray player with the Blueray IR remote
+- Turn on your surround sound with the speaker IR remote
+- Turn on your ceiling fan using a subGhz remote
+- Turn on your AC using another remote

+ 6 - 0
docs/changelog.md

@@ -0,0 +1,6 @@
+## v0.8
+
+First release to Application Catalog
+- IR Feature Working
+- Pause Features Working 
+- Added warning for block missing SubGhz Features

+ 76 - 0
helpers/xremote_custom_event.h

@@ -0,0 +1,76 @@
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+
+enum XRemoteCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    XRemoteCustomEventMenuVoid,
+    XRemoteCustomEventMenuSelected,
+    XRemoteCustomEventMenuAddSelected,
+    XRemoteCustomEventMenuAddIrSelected,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} XRemoteCustomEventMenu;
+#pragma pack(pop)
+
+typedef enum {
+    XRemoteCustomEventInfoscreenUp,
+    XRemoteCustomEventInfoscreenDown,
+    XRemoteCustomEventInfoscreenLeft,
+    XRemoteCustomEventInfoscreenRight,
+    XRemoteCustomEventInfoscreenOk,
+    XRemoteCustomEventInfoscreenBack,
+
+    XRemoteCustomEventCreateUp,
+    XRemoteCustomEventCreateDown,
+    XRemoteCustomEventCreateLeft,
+    XRemoteCustomEventCreateRight,
+    XRemoteCustomEventCreateOk,
+    XRemoteCustomEventCreateBack,
+
+    XRemoteCustomEventScene2Up,
+    XRemoteCustomEventScene2Down,
+    XRemoteCustomEventScene2Left,
+    XRemoteCustomEventScene2Right,
+    XRemoteCustomEventScene2Ok,
+    XRemoteCustomEventScene2Back,
+
+    XRemoteCustomEventTypePopupClosed,
+    XRemoteCustomEventTextInput,
+
+    XRemoteCustomEventPauseSetBack,
+    XRemoteCustomEventPauseSetUp,
+    XRemoteCustomEventPauseSetDown,
+    XRemoteCustomEventPauseSetOk,
+} XRemoteCustomEvent;
+
+static inline uint32_t xremote_custom_menu_event_pack(uint16_t type, int16_t value) {
+    XRemoteCustomEventMenu event = {.content = {.type = type, .value = value}};
+    return event.packed_value;
+}
+
+static inline void xremote_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
+    XRemoteCustomEventMenu event = {.packed_value = packed_value};
+    if(type) *type = event.content.type;
+    if(value) *value = event.content.value;
+}
+
+static inline uint16_t xremote_custom_menu_event_get_type(uint32_t packed_value) {
+    uint16_t type;
+    xremote_custom_menu_event_unpack(packed_value, &type, NULL);
+    return type;
+}
+
+static inline int16_t xremote_custom_menu_event_get_value(uint32_t packed_value) {
+    int16_t value;
+    xremote_custom_menu_event_unpack(packed_value, NULL, &value);
+    return value;
+}

+ 36 - 0
helpers/xremote_haptic.c

@@ -0,0 +1,36 @@
+#include "xremote_haptic.h"
+#include "../xremote.h"
+
+
+void xremote_play_happy_bump(void* context) {
+    XRemote* app = context;
+    if (app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void xremote_play_bad_bump(void* context) {
+    XRemote* app = context;
+    if (app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void xremote_play_long_bump(void* context) {
+    XRemote* app = context;
+    if (app->haptic != 1) {
+        return;
+    }
+    for (int i = 0; i < 4; i++) {
+        notification_message(app->notification, &sequence_set_vibro_on);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 10 - 0
helpers/xremote_haptic.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <notification/notification_messages.h>
+
+void xremote_play_happy_bump(void* context);
+
+void xremote_play_bad_bump(void* context);
+
+void xremote_play_long_bump(void* context);
+

+ 36 - 0
helpers/xremote_led.c

@@ -0,0 +1,36 @@
+#include "xremote_led.h"
+
+void xremote_led_set_rgb(void* context, int red, int green, int blue) {
+    XRemote* app = context;
+    if (app->led != 1) {
+        return;
+    }
+    NotificationMessage notification_led_message_1;
+    notification_led_message_1.type = NotificationMessageTypeLedRed;
+    NotificationMessage notification_led_message_2;
+    notification_led_message_2.type = NotificationMessageTypeLedGreen;
+    NotificationMessage notification_led_message_3;
+    notification_led_message_3.type = NotificationMessageTypeLedBlue;
+
+    notification_led_message_1.data.led.value = red;
+    notification_led_message_2.data.led.value = green;
+    notification_led_message_3.data.led.value = blue;
+    const NotificationSequence notification_sequence = {
+        &notification_led_message_1,
+        &notification_led_message_2,
+        &notification_led_message_3,
+        &message_do_not_reset,
+        NULL,
+    };
+    notification_message(app->notification, &notification_sequence);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set    
+}
+
+void xremote_led_reset(void* context) {
+    XRemote* app = context;
+    notification_message(app->notification, &sequence_reset_red);
+    notification_message(app->notification, &sequence_reset_green);
+    notification_message(app->notification, &sequence_reset_blue);
+    
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set    
+}

+ 8 - 0
helpers/xremote_led.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include "../xremote.h"
+
+void xremote_led_set_rgb(void* context, int red, int green, int blue);
+
+void xremote_led_reset(void* context);
+

+ 26 - 0
helpers/xremote_speaker.c

@@ -0,0 +1,26 @@
+#include "xremote_speaker.h"
+
+#define NOTE_INPUT 587.33f
+
+void xremote_play_input_sound(void* context) {
+    XRemote* app = context;
+    if (app->speaker != 1) {
+        return;
+    }
+    float volume = 1.0f;
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_INPUT, volume);
+    }
+    
+}
+
+void xremote_stop_all_sound(void* context) {
+    XRemote* app = context;
+    if (app->speaker != 1) {
+        return;
+    }
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
+}

+ 8 - 0
helpers/xremote_speaker.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include "../xremote.h"
+
+#define NOTE_INPUT 587.33f
+
+void xremote_play_input_sound(void* context);
+void xremote_stop_all_sound(void* context);

+ 120 - 0
helpers/xremote_storage.c

@@ -0,0 +1,120 @@
+#include "xremote_storage.h"
+
+
+static Storage* xremote_open_storage() {
+    return furi_record_open(RECORD_STORAGE);
+}
+
+static void xremote_close_storage() {
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void xremote_close_config_file(FlipperFormat* file) {
+    if (file == NULL) return;
+    flipper_format_file_close(file);
+    flipper_format_free(file);
+}
+
+void xremote_save_settings(void* context) {
+    XRemote* app = context;
+    if (app->save_settings == 0) {
+        return;
+    }
+
+    FURI_LOG_D(TAG, "Saving Settings");
+    Storage* storage = xremote_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+    
+    // Overwrite wont work, so delete first
+    if(storage_file_exists(storage, XREMOTE_SETTINGS_SAVE_PATH)) {
+        storage_simply_remove(storage, XREMOTE_SETTINGS_SAVE_PATH);
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, XREMOTE_SETTINGS_SAVE_PATH, NULL) == FSE_OK) {
+        FURI_LOG_D(TAG, "Config file %s is not found. Will create new.", XREMOTE_SETTINGS_SAVE_PATH);
+        if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
+            FURI_LOG_D(
+                TAG,
+                "Directory %s doesn't exist. Will create new.",
+                CONFIG_FILE_DIRECTORY_PATH);
+            if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+                FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
+            }
+        }
+    }
+
+    if(!flipper_format_file_open_new(fff_file, XREMOTE_SETTINGS_SAVE_PATH)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error creating new file %s", XREMOTE_SETTINGS_SAVE_PATH);
+        xremote_close_storage();
+        return;
+    }
+    
+    // Store Settings
+    flipper_format_write_header_cstr(
+        fff_file, XREMOTE_SETTINGS_HEADER, XREMOTE_SETTINGS_FILE_VERSION);
+    flipper_format_write_uint32(
+        fff_file, XREMOTE_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_write_uint32(
+        fff_file, XREMOTE_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_write_uint32(
+        fff_file, XREMOTE_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_write_uint32(
+        fff_file, XREMOTE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+    
+    if(!flipper_format_rewind(fff_file)) {
+        xremote_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Rewind error");
+        xremote_close_storage();
+        return;
+    }
+
+    xremote_close_config_file(fff_file);
+    xremote_close_storage();
+}
+
+void xremote_read_settings(void* context) {
+    XRemote* app = context;
+    Storage* storage = xremote_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    if(storage_common_stat(storage, XREMOTE_SETTINGS_SAVE_PATH, NULL) != FSE_OK) {
+        xremote_close_config_file(fff_file);
+        xremote_close_storage();
+        return;
+    }
+    uint32_t file_version;
+    FuriString* temp_str = furi_string_alloc();
+
+    if (!flipper_format_file_open_existing(fff_file, XREMOTE_SETTINGS_SAVE_PATH)) {
+        FURI_LOG_E(TAG, "Cannot open file %s", XREMOTE_SETTINGS_SAVE_PATH);
+        xremote_close_config_file(fff_file);
+        xremote_close_storage();
+        return;
+    }
+
+    if(!flipper_format_read_header(fff_file, temp_str, &file_version)) {
+        FURI_LOG_E(TAG, "Missing Header Data");
+        xremote_close_config_file(fff_file);
+        xremote_close_storage();
+        return;
+    }
+
+    if(file_version < XREMOTE_SETTINGS_FILE_VERSION) {
+        FURI_LOG_I(TAG, "old config version, will be removed.");
+        xremote_close_config_file(fff_file);
+        xremote_close_storage();
+        return;
+    }
+
+    flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    flipper_format_rewind(fff_file);
+
+    xremote_close_config_file(fff_file);
+    xremote_close_storage();
+}

+ 16 - 0
helpers/xremote_storage.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include "../xremote.h"
+
+#define XREMOTE_SETTINGS_FILE_VERSION 1
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/xremote")
+#define XREMOTE_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/xremote.conf"
+#define XREMOTE_SETTINGS_SAVE_PATH_TMP XREMOTE_SETTINGS_SAVE_PATH ".tmp"
+#define XREMOTE_SETTINGS_HEADER "Xremote Config File"
+#define XREMOTE_SETTINGS_KEY_HAPTIC "Haptic"
+#define XREMOTE_SETTINGS_KEY_LED "Led"
+#define XREMOTE_SETTINGS_KEY_SPEAKER "Speaker"
+#define XREMOTE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
+
+void xremote_save_settings(void* context);
+void xremote_read_settings(void* context);

BIN
icons/ButtonDown_10x5.png


BIN
icons/ButtonUp_10x5.png


BIN
icons/ir_10px.png


BIN
icons/ir_transmit_128x64.png


BIN
icons/pause_128x64.png


BIN
icons/sg_10px.png


BIN
icons/sg_transmit_128x64.png


BIN
icons/xr_10px.png


BIN
icons/xremote_10px.png


+ 265 - 0
models/cross/xremote_remote.c

@@ -0,0 +1,265 @@
+#include "xremote_remote.h"
+
+ARRAY_DEF(CrossRemoteItemArray, CrossRemoteItem*, M_PTR_OPLIST);
+
+struct CrossRemote {
+    FuriString* name;
+    FuriString* path;
+    CrossRemoteItemArray_t items;
+    int transmitting;
+};
+
+static void cross_remote_clear_items(CrossRemote* remote) {
+    CrossRemoteItemArray_it_t it;
+    for(CrossRemoteItemArray_it(it, remote->items); !CrossRemoteItemArray_end_p(it);
+        CrossRemoteItemArray_next(it)) {
+        xremote_remote_item_free(*CrossRemoteItemArray_cref(it));
+    }
+    CrossRemoteItemArray_reset(remote->items);
+}
+
+static void cross_remote_find_vacant_remote_name(FuriString* name, const char* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    FuriString* base_path;
+    base_path = furi_string_alloc_set(path);
+
+    if(furi_string_end_with(base_path, XREMOTE_APP_EXTENSION)) {
+        size_t filename_start = furi_string_search_rchar(base_path, '/');
+        furi_string_left(base_path, filename_start);
+    }
+
+    furi_string_printf(
+        base_path, "%s/%s%s", path, furi_string_get_cstr(name), XREMOTE_APP_EXTENSION);
+
+    FS_Error status = storage_common_stat(storage, furi_string_get_cstr(base_path), NULL);
+
+    if(status == FSE_OK) {
+        // If name is taken, try another name2, name3 etc
+        size_t dot = furi_string_search_rchar(base_path, '.');
+        furi_string_left(base_path, dot);
+
+        FuriString* path_temp;
+        path_temp = furi_string_alloc();
+
+        uint32_t i = 1;
+        do {
+            furi_string_printf(
+                path_temp, "%s%lu%s", furi_string_get_cstr(base_path), ++i, XREMOTE_APP_EXTENSION);
+            status = storage_common_stat(storage, furi_string_get_cstr(path_temp), NULL);
+        } while(status == FSE_OK);
+
+        furi_string_free(path_temp);
+
+        if(status == FSE_NOT_EXIST) {
+            furi_string_cat_printf(name, "%lu", i);
+        }
+    }
+
+    furi_string_free(base_path);
+    furi_record_close(RECORD_STORAGE);
+}
+
+CrossRemote* cross_remote_alloc() {
+    CrossRemote* remote = malloc(sizeof(CrossRemote));
+    CrossRemoteItemArray_init(remote->items);
+    remote->name = furi_string_alloc();
+    remote->path = furi_string_alloc();
+    remote->transmitting = 0;
+    return remote;
+}
+
+void cross_remote_set_transmitting(CrossRemote* remote, int status) {
+    remote->transmitting = status;
+}
+
+int cross_remote_get_transmitting(CrossRemote* remote) {
+    return remote->transmitting;
+}
+
+void cross_remote_free(CrossRemote* remote) {
+    furi_string_free(remote->name);
+    furi_string_free(remote->path);
+    cross_remote_clear_items(remote);
+    CrossRemoteItemArray_clear(remote->items);
+    free(remote);
+}
+
+const char* cross_remote_get_name(CrossRemote* remote) {
+    return furi_string_get_cstr(remote->name);
+}
+
+bool cross_remote_add_ir_item(CrossRemote* remote, const char* name, InfraredSignal* signal) {
+    CrossRemoteItem* item = xremote_remote_item_alloc();
+    xremote_remote_item_set_type(item, XRemoteRemoteItemTypeInfrared);
+    xremote_remote_item_set_name(item, name);
+    xremote_remote_item_set_ir_signal(item, signal);
+    CrossRemoteItemArray_push_back(remote->items, item);
+    return true;
+}
+
+bool cross_remote_add_pause(CrossRemote* remote, int time) {
+    CrossRemoteItem* item = xremote_remote_item_alloc();
+    xremote_remote_item_set_type(item, XRemoteRemoteItemTypePause);
+    char name[9];
+    snprintf(name, 9, CROSS_REMOTE_PAUSE_NAME, time);
+    xremote_remote_item_set_name(item, name);
+    xremote_remote_item_set_time(item, time);
+    CrossRemoteItemArray_push_back(remote->items, item);
+    return true;
+}
+
+bool cross_remote_add_subghz(CrossRemote* remote, SubGhzRemote* subghz) {
+    UNUSED(subghz);
+    CrossRemoteItem* item = xremote_remote_item_alloc();
+    xremote_remote_item_set_type(item, XRemoteRemoteItemTypeSubGhz);
+    xremote_remote_item_set_name(item, xremote_sg_remote_get_name(subghz));
+    xremote_remote_item_set_sg_signal(item, subghz);
+
+    CrossRemoteItemArray_push_back(remote->items, item);
+    return true;
+}
+
+size_t cross_remote_get_item_count(CrossRemote* remote) {
+    return CrossRemoteItemArray_size(remote->items);
+}
+
+CrossRemoteItem* cross_remote_get_item(CrossRemote* remote, size_t index) {
+    furi_assert(index < CrossRemoteItemArray_size(remote->items));
+    return *CrossRemoteItemArray_get(remote->items, index);
+}
+
+void cross_remote_remove_item(CrossRemote* remote, size_t index) {
+    CrossRemoteItemArray_erase(remote->items, index);
+}
+
+void cross_remote_rename_item(CrossRemote* remote, size_t index, const char* name) {
+    CrossRemoteItem* item = cross_remote_get_item(remote, index);
+    xremote_remote_item_set_name(item, name);
+}
+
+
+bool cross_remote_load(CrossRemote* remote, FuriString* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
+    FuriString* buf;
+    buf = furi_string_alloc();
+
+    FURI_LOG_I(TAG, "loading file: \'%s\'", furi_string_get_cstr(path));
+    bool success = false;
+    do {
+        // File not found
+        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
+        uint32_t version;
+        // Read Version & Type
+        if(!flipper_format_read_header(ff, buf, &version)) break;
+        if(!furi_string_equal(buf, XREMOTE_FILE_TYPE) || (version != XREMOTE_FILE_VERSION)) break;
+
+        // Init Remote
+        path_extract_filename(path, buf, true);
+        cross_remote_clear_items(remote);
+        cross_remote_set_name(remote, furi_string_get_cstr(buf));
+        cross_remote_set_path(remote, furi_string_get_cstr(path));
+        // Load Items
+        for(bool can_read = true; can_read;) {
+            CrossRemoteItem* item = xremote_remote_item_alloc();
+            can_read = xremote_remote_item_read(item, ff);
+            if(can_read) {
+                CrossRemoteItemArray_push_back(remote->items, item);
+            } else {
+                xremote_remote_item_free(item);
+            }
+        }
+        success = true;
+    } while(false);
+
+    furi_string_free(buf);
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}
+
+void cross_remote_set_name(CrossRemote* remote, const char* name) {
+    furi_string_set(remote->name, name);
+}
+
+void cross_remote_set_path(CrossRemote* remote, const char* path) {
+    furi_string_set(remote->path, path);
+}
+
+bool cross_remote_save_new(CrossRemote* remote, const char* name) {
+    FuriString *new_name, *new_path;
+    new_name = furi_string_alloc_set(name);
+    new_path = furi_string_alloc_set(XREMOTE_APP_FOLDER);
+    
+    cross_remote_find_vacant_remote_name(new_name, furi_string_get_cstr(new_path));
+    furi_string_cat_printf(
+        new_path, "/%s%s", furi_string_get_cstr(new_name), XREMOTE_APP_EXTENSION);
+
+    cross_remote_set_name(remote, furi_string_get_cstr(new_name));
+    cross_remote_set_path(remote, furi_string_get_cstr(new_path));
+
+    furi_string_free(new_name);
+    furi_string_free(new_path);
+    return cross_remote_store(remote);
+}
+
+void cross_remote_reset(CrossRemote* remote) {
+    furi_string_reset(remote->name);
+    furi_string_reset(remote->path);
+    CrossRemoteItemArray_clear(remote->items);
+    remote->transmitting = 0;
+}
+
+bool cross_remote_delete(CrossRemote* remote) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FS_Error status = storage_common_remove(storage, furi_string_get_cstr(remote->path));
+
+    cross_remote_reset(remote);
+
+    furi_record_close(RECORD_STORAGE);
+    return (status == FSE_OK || status == FSE_NOT_EXIST);
+}
+
+bool cross_remote_store(CrossRemote* remote) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    const char* path = furi_string_get_cstr(remote->path);
+
+    FURI_LOG_I(TAG, "Storing file: \'%s\'", path);
+
+    bool success = flipper_format_file_open_always(ff, path) &&
+                   flipper_format_write_header_cstr(ff, XREMOTE_FILE_TYPE, XREMOTE_FILE_VERSION);
+
+    // save Items
+    if(success) {
+        CrossRemoteItemArray_it_t it;
+        for(CrossRemoteItemArray_it(it, remote->items); !CrossRemoteItemArray_end_p(it);
+            CrossRemoteItemArray_next(it)) {
+            CrossRemoteItem* item = *CrossRemoteItemArray_cref(it);
+            success = false;
+            if (item->type == XRemoteRemoteItemTypeInfrared) {
+                success = xremote_ir_signal_save(
+                    xremote_remote_item_get_ir_signal(item),
+                    ff,
+                    xremote_remote_item_get_name(item));
+            } else if(item->type == XRemoteRemoteItemTypePause) {
+                success = xremote_pause_save(ff, 
+                    item->time,
+                    xremote_remote_item_get_name(item));
+            } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
+                success = xremote_sg_signal_save(
+                    xremote_remote_item_get_sg_signal(item),
+                    ff,
+                    xremote_remote_item_get_name(item));
+            }
+            if(!success) {
+                break;
+            }
+        }
+    }
+
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 28 - 0
models/cross/xremote_remote.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include "xremote_remote_item.h"
+#include "../../xremote_i.h"
+
+#define CROSS_REMOTE_PAUSE_NAME "Pause %ds"
+
+CrossRemote* cross_remote_alloc();
+
+void cross_remote_free(CrossRemote* cross_remote);
+bool cross_remote_load(CrossRemote* cross_remote, FuriString* path);
+const char* cross_remote_get_name(CrossRemote* remote);
+void cross_remote_set_transmitting(CrossRemote* remote, int status);
+int cross_remote_get_transmitting(CrossRemote* remote);
+bool cross_remote_add_pause(CrossRemote* remote, int time);
+bool cross_remote_add_ir_item(CrossRemote* remote, const char* name, InfraredSignal* signal);
+bool cross_remote_add_subghz(CrossRemote* remote, SubGhzRemote* subghz);
+void cross_remote_remove_item(CrossRemote* remote, size_t index);
+void cross_remote_rename_item(CrossRemote* remote, size_t index, const char* name);
+size_t cross_remote_get_item_count(CrossRemote* remote);
+CrossRemoteItem* cross_remote_get_item(CrossRemote* remote, size_t index);
+
+void cross_remote_set_name(CrossRemote* remote, const char* name);
+void cross_remote_set_path(CrossRemote* remote, const char* path);
+bool cross_remote_save_new(CrossRemote* remote, const char* name);
+bool cross_remote_store(CrossRemote* remote);
+void cross_remote_reset(CrossRemote* remote);
+bool cross_remote_delete(CrossRemote* remote);

+ 241 - 0
models/cross/xremote_remote_item.c

@@ -0,0 +1,241 @@
+#include "xremote_remote_item.h"
+
+
+CrossRemoteItem* xremote_remote_item_alloc() {
+    CrossRemoteItem* item = malloc(sizeof(CrossRemoteItem));
+    item->name = furi_string_alloc();
+    item->time = 0;
+    item->type = 0;
+    item->ir_signal = xremote_ir_signal_alloc();
+    item->sg_signal = xremote_sg_remote_alloc();
+    
+    return item;
+}
+
+static inline bool xremote_ir_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 xremote_ir_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 xremote_sg_signal_save_data(SubGhzRemote* remote, FlipperFormat* ff) {
+    UNUSED(remote);
+    UNUSED(ff);
+    return true;
+    /*return flipper_format_write_uint32(ff, "frequency", xremote_sg_remote_get_frequency(remote)) &&
+           flipper_format_write_string_cstr(ff, "preset", xremote_sg_remote_get_preset(remote));*/
+}
+
+static bool xremote_ir_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;
+}
+
+bool xremote_remote_item_read(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* type = furi_string_alloc();
+    bool success = false;
+    do {
+        if(!flipper_format_read_string(ff, "remote_type", type)) break;
+        if(furi_string_equal(type, "IR")) {
+            success = xremote_remote_item_read_ir(item, ff);
+        } else if(furi_string_equal(type, "PAUSE")) {
+            success = xremote_remote_item_read_pause(item, ff);
+        } else {
+            break;
+        }
+        success = true;
+    } while(false);
+    return success;
+}
+
+bool xremote_remote_item_read_ir(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* buf;
+    bool success = false;
+    buf = furi_string_alloc();
+    item->type = XRemoteRemoteItemTypeInfrared;
+    item->time = 0;
+    
+    do {
+        if(!flipper_format_read_string(ff, "name", item->name)) break;
+        if(!flipper_format_read_string(ff, "type", buf)) break;
+        if(furi_string_equal(buf, "raw")) {
+            if(!xremote_remote_item_read_ir_signal_raw(item, ff)) break;
+        } else if(furi_string_equal(buf, "parsed")) {
+            if(!xremote_remote_item_read_message(item, ff)) break;
+        } else {
+            break;
+        }
+        success = true;
+    } while(false);
+    furi_string_free(buf);
+
+    return success;
+}
+
+bool xremote_remote_item_read_pause(CrossRemoteItem* item, FlipperFormat* ff) {
+    bool success = false;
+    item->type = XRemoteRemoteItemTypePause;
+
+    do {
+        if(!flipper_format_read_string(ff, "name", item->name)) break;
+        if(!flipper_format_read_int32(ff, "time", &item->time, 1)) break;
+        success = true;
+    } while(false);
+
+    return success;
+}
+
+bool xremote_remote_item_read_ir_signal_raw(CrossRemoteItem* item, 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) {
+        xremote_ir_signal_set_raw_signal(item->ir_signal, timings, timings_size, frequency, duty_cycle);
+    }
+
+    free(timings);
+
+    return success;
+}
+
+bool xremote_remote_item_read_message(CrossRemoteItem* item, FlipperFormat* ff) {
+    FuriString* buf = furi_string_alloc();
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "protocol", buf)) break;
+        InfraredMessage message;
+        message.protocol = infrared_get_protocol_by_name(furi_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) &&
+                  xremote_ir_signal_is_message_valid(&message);
+
+        if(!success) break;
+
+        xremote_ir_signal_set_message(item->ir_signal, &message);
+    } while(false);
+
+    furi_string_free(buf);
+    return success;
+}
+
+
+void xremote_remote_item_free(CrossRemoteItem* item) {
+    furi_string_free(item->name);
+    //Determine type before free
+    //xremote_ir_signal_free(item->ir_signal);
+    //xremote_sg_remote_free(item->sg_signal);
+    free(item);
+}
+
+void xremote_remote_item_set_type(CrossRemoteItem* item, int type) {
+    item->type = type;
+}
+
+void xremote_remote_item_set_name(CrossRemoteItem* item, const char* name) {
+    furi_string_set(item->name, name);
+}
+
+void xremote_remote_item_set_time(CrossRemoteItem* item, int32_t time) {
+    item->time = time;
+}
+
+void xremote_remote_item_set_ir_signal(CrossRemoteItem* item, InfraredSignal* signal) {
+    xremote_ir_signal_set_signal(item->ir_signal, signal);
+}
+
+void xremote_remote_item_set_sg_signal(CrossRemoteItem* item, SubGhzRemote* subghz) {
+    item->sg_signal = subghz;
+}
+
+const char* xremote_remote_item_get_name(CrossRemoteItem* item) {
+    return furi_string_get_cstr(item->name);
+}
+
+InfraredSignal* xremote_remote_item_get_ir_signal(CrossRemoteItem* item) {
+    return item->ir_signal;
+}
+
+SubGhzRemote* xremote_remote_item_get_sg_signal(CrossRemoteItem* item) {
+    return item->sg_signal;
+}
+
+bool xremote_ir_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
+    if(!flipper_format_write_comment_cstr(ff, "") || 
+       !flipper_format_write_string_cstr(ff, "remote_type", "IR") || 
+       !flipper_format_write_string_cstr(ff, "name", name)) {
+        return false;
+    } else if(signal->is_raw) {
+        return xremote_ir_signal_save_raw(&signal->payload.raw, ff);
+    } else {
+        return xremote_ir_signal_save_message(&signal->payload.message, ff);
+    }
+}
+
+bool xremote_pause_save(FlipperFormat* ff, int32_t time, const char* name) {
+    if(!flipper_format_write_comment_cstr(ff, "") ||
+       !flipper_format_write_string_cstr(ff, "remote_type", "PAUSE") ||
+       !flipper_format_write_string_cstr(ff, "name", name) || 
+       !flipper_format_write_int32(ff, "time", &time, 1)) {
+       return false;
+    }
+    return true;
+}
+
+bool xremote_sg_signal_save(SubGhzRemote* remote, FlipperFormat* ff, const char* name) {
+    if(!flipper_format_write_comment_cstr(ff, "") || 
+       !flipper_format_write_string_cstr(ff, "remote_type", "SG") || 
+       !flipper_format_write_string_cstr(ff, "name", name)) {
+        return false;
+    }
+    return xremote_sg_signal_save_data(remote, ff);
+}

+ 34 - 0
models/cross/xremote_remote_item.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include "../infrared/xremote_ir_signal.h"
+#include "../subghz/xremote_sg_remote.h"
+#include "../subghz/subghz_i.h"
+#include "../../xremote_i.h"
+
+
+bool xremote_remote_item_read(CrossRemoteItem* item, FlipperFormat* ff);
+bool xremote_remote_item_read_ir(CrossRemoteItem* item, FlipperFormat* ff);
+bool xremote_remote_item_read_ir_signal_raw(CrossRemoteItem* item, FlipperFormat* ff);
+bool xremote_remote_item_read_message(CrossRemoteItem* item, FlipperFormat* ff);
+
+bool xremote_remote_item_read_pause(CrossRemoteItem* item, FlipperFormat* ff);
+
+CrossRemoteItem* xremote_remote_item_alloc();
+void xremote_remote_item_free(CrossRemoteItem* item);
+
+void xremote_remote_item_set_name(CrossRemoteItem* item, const char* name);
+const char* xremote_remote_item_get_name(CrossRemoteItem* item);
+
+void xremote_remote_item_set_type(CrossRemoteItem* item, int type);
+void xremote_remote_item_set_time(CrossRemoteItem* item, int32_t time);
+
+InfraredSignal* xremote_remote_item_get_ir_signal(CrossRemoteItem* item);
+void xremote_remote_item_set_ir_signal(CrossRemoteItem* item, InfraredSignal* signal);
+SubGhzRemote* xremote_remote_item_get_sg_signal(CrossRemoteItem* item);
+void xremote_remote_item_set_sg_signal(CrossRemoteItem* item, SubGhzRemote* subghz);
+
+bool xremote_pause_save(FlipperFormat* ff, int32_t time, const char* name);
+bool xremote_ir_signal_save(InfraredSignal* signal, FlipperFormat* ff, const char* name);
+bool xremote_sg_signal_save(SubGhzRemote* remote, FlipperFormat* ff, const char* name);
+
+

+ 90 - 0
models/infrared/xremote_ir_remote.c

@@ -0,0 +1,90 @@
+#include "xremote_ir_remote.h"
+
+#define TAG "XremoteInfraredRemote"
+
+ARRAY_DEF(InfraredButtonArray, InfraredRemoteButton*, M_PTR_OPLIST);
+
+struct InfraredRemote {
+    InfraredButtonArray_t buttons;
+    FuriString* name;
+    FuriString* path;
+};
+
+InfraredRemote* xremote_ir_remote_alloc() {
+    InfraredRemote* remote = malloc(sizeof(InfraredRemote));
+    InfraredButtonArray_init(remote->buttons);
+    remote->name = furi_string_alloc();
+    remote->path = furi_string_alloc();
+    return remote;
+}
+
+const char* xremote_ir_remote_get_name(InfraredRemote* remote) {
+    return furi_string_get_cstr(remote->name);
+}
+
+static void xremote_ir_remote_clear_buttons(InfraredRemote* remote) {
+    InfraredButtonArray_it_t it;
+    for(InfraredButtonArray_it(it, remote->buttons); !InfraredButtonArray_end_p(it);
+        InfraredButtonArray_next(it)) {
+        xremote_ir_remote_button_free(*InfraredButtonArray_cref(it));
+    }
+    InfraredButtonArray_reset(remote->buttons);
+}
+
+void xremote_ir_remote_free(InfraredRemote* remote) {
+    furi_string_free(remote->path);
+    furi_string_free(remote->name);
+    free(remote);
+}
+
+InfraredRemoteButton* xremote_ir_get_button(InfraredRemote* remote, size_t index) {
+    furi_assert(index < InfraredButtonArray_size(remote->buttons));
+    return *InfraredButtonArray_get(remote->buttons, index);
+}
+
+bool xremote_ir_remote_load(InfraredRemote* remote, FuriString* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
+
+    FuriString* buf;
+    buf = furi_string_alloc();
+
+    FURI_LOG_I(TAG, "loading IR Remote: \'%s\'", furi_string_get_cstr(path));
+    bool success = false;
+
+    do {
+        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
+        uint32_t version;
+        if(!flipper_format_read_header(ff, buf, &version)) break;
+        if(!furi_string_equal(buf, "IR signals file") || (version != 1)) break;
+
+        path_extract_filename(path, buf, true);
+        xremote_ir_remote_clear_buttons(remote);
+
+        for(bool can_read = true; can_read;) {
+            InfraredRemoteButton* button = xremote_ir_remote_button_alloc();
+            can_read = xremote_ir_signal_read(xremote_ir_remote_button_get_signal(button), ff, buf);
+            if (can_read) {
+                xremote_ir_remote_button_set_name(button, furi_string_get_cstr(buf));
+                InfraredButtonArray_push_back(remote->buttons, button);
+            } else {
+                xremote_ir_remote_button_free(button);
+            }
+        }
+        success = true;
+    } while(false);
+
+    furi_string_free(buf);
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}
+
+size_t xremote_ir_remote_get_button_count(InfraredRemote* remote) {
+    return InfraredButtonArray_size(remote->buttons);
+}
+
+InfraredRemoteButton* xremote_ir_remote_get_button(InfraredRemote* remote, size_t index) {
+    furi_assert(index < InfraredButtonArray_size(remote->buttons));
+    return *InfraredButtonArray_get(remote->buttons, index);
+}

+ 22 - 0
models/infrared/xremote_ir_remote.h

@@ -0,0 +1,22 @@
+#pragma once
+
+//#include "../../xremote_i.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <m-array.h>
+#include <toolbox/path.h>
+#include <storage/storage.h>
+#include <core/common_defines.h>
+#include "xremote_ir_remote_button.h"
+
+typedef struct InfraredRemote InfraredRemote;
+
+InfraredRemote* xremote_ir_remote_alloc();
+void xremote_ir_remote_free(InfraredRemote* remote);
+
+const char* xremote_ir_remote_get_name(InfraredRemote* remote);
+
+bool xremote_ir_remote_load(InfraredRemote* remote, FuriString* path);
+size_t xremote_ir_remote_get_button_count(InfraredRemote* remote);
+InfraredRemoteButton* xremote_ir_remote_get_button(InfraredRemote* remote, size_t index);

+ 33 - 0
models/infrared/xremote_ir_remote_button.c

@@ -0,0 +1,33 @@
+#include "xremote_ir_remote_button.h"
+
+#include <stdlib.h>
+
+struct InfraredRemoteButton {
+    FuriString* name;
+    InfraredSignal* signal;
+};
+
+InfraredRemoteButton* xremote_ir_remote_button_alloc() {
+    InfraredRemoteButton* button = malloc(sizeof(InfraredRemoteButton));
+    button->name = furi_string_alloc();
+    button->signal = xremote_ir_signal_alloc();
+    return button;
+}
+
+void xremote_ir_remote_button_free(InfraredRemoteButton* button) {
+    furi_string_free(button->name);
+    xremote_ir_signal_free(button->signal);
+    free(button);
+}
+
+void xremote_ir_remote_button_set_name(InfraredRemoteButton* button, const char* name) {
+    furi_string_set(button->name, name);
+}
+
+const char* xremote_ir_remote_button_get_name(InfraredRemoteButton* button) {
+    return furi_string_get_cstr(button->name);
+}
+
+InfraredSignal* xremote_ir_remote_button_get_signal(InfraredRemoteButton* button) {
+    return button->signal;
+}

+ 13 - 0
models/infrared/xremote_ir_remote_button.h

@@ -0,0 +1,13 @@
+#pragma once
+
+//#include "../../xremote_i.h"
+#include "xremote_ir_signal.h"
+//#include "../xremote.h"
+
+typedef struct InfraredRemoteButton InfraredRemoteButton;
+
+InfraredRemoteButton* xremote_ir_remote_button_alloc();
+void xremote_ir_remote_button_free(InfraredRemoteButton* button);
+void xremote_ir_remote_button_set_name(InfraredRemoteButton* button, const char* name);
+const char* xremote_ir_remote_button_get_name(InfraredRemoteButton* button);
+InfraredSignal* xremote_ir_remote_button_get_signal(InfraredRemoteButton* button);

+ 218 - 0
models/infrared/xremote_ir_signal.c

@@ -0,0 +1,218 @@
+#include "xremote_ir_signal.h"
+#include "../../xremote_i.h"
+
+static void xremote_ir_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 xremote_ir_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 xremote_ir_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 (%X - %X): %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 - %X): %zX",
+            MAX_TIMINGS_AMOUNT,
+            raw->timings_size);
+        return false;
+    }
+
+    return true;
+}
+
+static inline bool xremote_ir_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) {
+    FuriString* buf;
+    buf = furi_string_alloc();
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "protocol", buf)) break;
+
+        InfraredMessage message;
+        message.protocol = infrared_get_protocol_by_name(furi_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) &&
+                  xremote_ir_signal_is_message_valid(&message);
+
+        if(!success) break;
+
+        xremote_ir_signal_set_message(signal, &message);
+    } while(0);
+
+    return success;
+}
+
+static inline bool xremote_ir_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) {
+        xremote_ir_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
+    }
+
+    free(timings);
+    return success;
+}
+
+InfraredSignal* xremote_ir_signal_alloc() {
+    InfraredSignal* signal = malloc(sizeof(InfraredSignal));
+
+    signal->is_raw = false;
+    signal->payload.message.protocol = InfraredProtocolUnknown;
+
+    return signal;
+}
+
+void xremote_ir_signal_free(InfraredSignal* signal) {
+    xremote_ir_signal_clear_timings(signal);
+    free(signal);
+}
+
+bool xremote_ir_signal_is_raw(InfraredSignal* signal) {
+    return signal->is_raw;
+}
+
+bool xremote_ir_signal_is_valid(InfraredSignal* signal) {
+    return signal->is_raw ? xremote_ir_signal_is_raw_valid(&signal->payload.raw) :
+                            xremote_ir_signal_is_message_valid(&signal->payload.message);
+}
+
+void xremote_ir_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) {
+    if(other->is_raw) {
+        const InfraredRawSignal* raw = &other->payload.raw;
+        xremote_ir_signal_set_raw_signal(
+            signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
+    } else {
+        const InfraredMessage* message = &other->payload.message;
+        xremote_ir_signal_set_message(signal, message);
+    }
+}
+
+void xremote_ir_signal_set_raw_signal(
+    InfraredSignal* signal,
+    const uint32_t* timings,
+    size_t timings_size,
+    uint32_t frequency,
+    float duty_cycle) {
+    xremote_ir_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* xremote_ir_signal_get_raw_signal(InfraredSignal* signal) {
+    furi_assert(signal->is_raw);
+    return &signal->payload.raw;
+}
+
+static bool xremote_ir_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) {
+    FuriString* tmp = furi_string_alloc();
+
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "type", tmp)) break;
+        if(furi_string_equal(tmp, "raw")) {
+            success = xremote_ir_signal_read_raw(signal, ff);
+        } else if(furi_string_equal(tmp, "parsed")) {
+            success = xremote_ir_signal_read_message(signal, ff);
+        } else {
+            FURI_LOG_E(TAG, "Unknown signal type");
+        }
+    } while(false);
+
+    furi_string_free(tmp);
+    return success;
+}
+
+bool xremote_ir_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) {
+    FuriString* tmp = furi_string_alloc();
+
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_string(ff, "name", tmp)) break;
+        furi_string_set(name, tmp);
+        if(!xremote_ir_signal_read_body(signal, ff)) break;
+        success = true;
+    } while(0);
+
+    furi_string_free(tmp);
+    return success;
+}
+
+void xremote_ir_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) {
+    xremote_ir_signal_clear_timings(signal);
+
+    signal->is_raw = false;
+    signal->payload.message = *message;
+}
+
+InfraredMessage* xremote_ir_signal_get_message(InfraredSignal* signal) {
+    furi_assert(!signal->is_raw);
+    return &signal->payload.message;
+}

+ 38 - 0
models/infrared/xremote_ir_signal.h

@@ -0,0 +1,38 @@
+#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* xremote_ir_signal_alloc();
+void xremote_ir_signal_free(InfraredSignal* signal);
+
+bool xremote_ir_signal_is_raw(InfraredSignal* signal);
+bool xremote_ir_signal_is_valid(InfraredSignal* signal);
+
+void xremote_ir_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other);
+
+void xremote_ir_signal_set_raw_signal(
+    InfraredSignal* signal, 
+    const uint32_t* timings, 
+    size_t timings_size, 
+    uint32_t frequency, 
+    float duty_cycle);
+InfraredRawSignal* xremote_ir_signal_get_raw_signal(InfraredSignal* signal);
+
+void xremote_ir_signal_set_message(InfraredSignal* signal, const InfraredMessage* message);
+InfraredMessage* xremote_ir_signal_get_message(InfraredSignal* signal);
+
+bool xremote_ir_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name);

+ 57 - 0
models/subghz/subghz_i.h

@@ -0,0 +1,57 @@
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <m-array.h>
+#include <toolbox/path.h>
+#include <storage/storage.h>
+#include <core/common_defines.h>
+#include <flipper_format/flipper_format.h>
+#include <lib/subghz/protocols/raw.h>
+//#include <lib/toolbox/path.h>
+//#include <flipper_format/flipper_format_i.h>
+#include <lib/toolbox/stream/stream.h>
+//#include <lib/subghz/protocols/protocol_items.h> //Not found
+#include <lib/subghz/subghz_worker.h>
+#include <lib/subghz/subghz_setting.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/devices/devices.h>
+//#include <lib/subghz/blocks/custom_btn.h>
+
+#include <flipper_format/flipper_format_i.h>
+
+#include "subghz_types.h"
+
+extern const SubGhzProtocolRegistry subghz_protocol_registry;
+
+struct SubGhzTxRx {
+    //SubGhzWorker* worker;
+
+    SubGhzEnvironment* environment;
+    SubGhzReceiver* receiver;
+    //SubGhzTransmitter* transmitter;
+    //SubGhzProtocolFlag filter;
+    SubGhzProtocolDecoderBase* decoder_result;
+    FlipperFormat* fff_data;
+
+    SubGhzRadioPreset* preset;
+    //SubGhzHistory* history;
+    //uint16_t idx_menu_chosen;
+    //SubGhzTxRxState txrx_state;
+    //SubGhzHopperState hopper_state;
+    //SubGhzSpeakerState speaker_state;
+    //uint8_t hopper_timeout;
+    //uint8_t hopper_idx_frequency;
+    //SubGhzRxKeyState rx_key_state;
+
+    //float raw_threshold_rssi;
+    //uint8_t raw_threshold_rssi_low_count;
+    const SubGhzDevice* radio_device;
+};
+
+typedef struct SubGhzTxRx SubGhzTxRx;
+
+

+ 79 - 0
models/subghz/subghz_types.h

@@ -0,0 +1,79 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzNotification state */
+typedef enum {
+    SubGhzNotificationStateStarting,
+    SubGhzNotificationStateIDLE,
+    SubGhzNotificationStateTx,
+    SubGhzNotificationStateRx,
+    SubGhzNotificationStateRxDone,
+} SubGhzNotificationState;
+
+/** SubGhzTxRx state */
+typedef enum {
+    SubGhzTxRxStateIDLE,
+    SubGhzTxRxStateRx,
+    SubGhzTxRxStateTx,
+    SubGhzTxRxStateSleep,
+} SubGhzTxRxState;
+
+/** SubGhzHopperState state */
+typedef enum {
+    SubGhzHopperStateOFF,
+    SubGhzHopperStateRunnig,
+    SubGhzHopperStatePause,
+    SubGhzHopperStateRSSITimeOut,
+} SubGhzHopperState;
+
+/** SubGhzSpeakerState state */
+typedef enum {
+    SubGhzSpeakerStateDisable,
+    SubGhzSpeakerStateShutdown,
+    SubGhzSpeakerStateEnable,
+} SubGhzSpeakerState;
+
+/** SubGhzRxKeyState state */
+typedef enum {
+    SubGhzRxKeyStateIDLE,
+    SubGhzRxKeyStateNoSave,
+    SubGhzRxKeyStateNeedSave,
+    SubGhzRxKeyStateBack,
+    SubGhzRxKeyStateStart,
+    SubGhzRxKeyStateAddKey,
+    SubGhzRxKeyStateExit,
+    SubGhzRxKeyStateRAWLoad,
+    SubGhzRxKeyStateRAWSave,
+} SubGhzRxKeyState;
+
+/** SubGhzLoadKeyState state */
+typedef enum {
+    SubGhzLoadKeyStateUnknown,
+    SubGhzLoadKeyStateOK,
+    SubGhzLoadKeyStateParseErr,
+    SubGhzLoadKeyStateProtocolDescriptionErr,
+} SubGhzLoadKeyState;
+
+/** SubGhzLock */
+typedef enum {
+    SubGhzLockOff,
+    SubGhzLockOn,
+} SubGhzLock;
+
+typedef enum {
+    SubGhzViewIdMenu,
+    SubGhzViewIdReceiver,
+    SubGhzViewIdPopup,
+    SubGhzViewIdTextInput,
+    SubGhzViewIdWidget,
+    SubGhzViewIdTransmitter,
+    SubGhzViewIdVariableItemList,
+    SubGhzViewIdFrequencyAnalyzer,
+    SubGhzViewIdReadRAW,
+
+    SubGhzViewIdStatic,
+    SubGhzViewIdTestCarrier,
+    SubGhzViewIdTestPacket,
+} SubGhzViewId;

+ 218 - 0
models/subghz/xremote_sg_remote.c

@@ -0,0 +1,218 @@
+#include "subghz/types.h"
+#include <lib/toolbox/path.h>
+#include <lib/subghz/types.h>
+#include <lib/subghz/subghz_worker.h>
+#include <lib/subghz/subghz_setting.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/registry.h>
+//#include <lib/subghz/protocols/keeloq.h>
+//#include <lib/subghz/protocols/secplus_v1.h>
+//#include <lib/subghz/protocols/secplus_v2.h>
+//#include <lib/subghz/protocols/princeton_for_testing.h>
+//#include <lib/subghz/blocks/math.h>
+//#include <lib/subghz/protocols/raw.h>
+//#include <lib/subghz/protocols/bin_raw.h>
+//#include <lib/subghz/protocols/protocol_items.h>
+//#include <lib/subghz/protocols/protocol_items.c>
+#include <lib/subghz/subghz_keystore.h>
+//#include <lib/subghz/subghz_file_encoder_worker.h>
+#include <gui/modules/variable_item_list.h>
+#include "xremote_sg_remote.h"
+
+#define TAG "Xremote"
+
+
+
+struct SubGhzRemote {
+    FuriString* name;
+    FuriString* path;
+    SubGhzTxRx* txrx;
+    uint32_t frequency;
+    SubGhzSetting* setting;
+};
+
+const char* xremote_sg_remote_get_name(SubGhzRemote* remote) {
+    return furi_string_get_cstr(remote->name);
+}
+
+void subghz_preset_init(
+    void* context,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size) {
+    furi_assert(context);
+    SubGhzRemote* remote = context;
+    furi_string_set(remote->txrx->preset->name, preset_name);
+    remote->txrx->preset->frequency = frequency;
+    remote->txrx->preset->data = preset_data;
+    remote->txrx->preset->data_size = preset_data_size;
+}
+
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_name(instance->radio_device);
+}
+
+SubGhzRemote* xremote_sg_remote_alloc() {
+    SubGhzRemote* remote = malloc(sizeof(SubGhzRemote));
+    remote->name = furi_string_alloc();
+    remote->path = furi_string_alloc();
+    
+    // SubGhz Settings
+    remote->setting = subghz_setting_alloc();
+    subghz_setting_load(remote->setting, EXT_PATH("subghz/assets/setting_user"));
+
+    remote->txrx = malloc(sizeof(SubGhzTxRx));
+    remote->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
+    remote->txrx->preset->name = furi_string_alloc();
+    subghz_preset_init(
+        remote, "AM650", subghz_setting_get_default_frequency(remote->setting), NULL, 0);
+    remote->txrx->fff_data = flipper_format_string_alloc();
+    remote->txrx->environment = subghz_environment_alloc();
+    /*subghz_environment_set_came_atomo_rainbow_table_file_name(
+        remote->txrx->environment, EXT_PATH("subghz/assets/came_atomo"));
+    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
+        remote->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n"));
+    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
+        remote->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s"));
+    subghz_environment_set_protocol_registry(
+        remote->txrx->environment, (void*)&subghz_protocol_registry);
+    remote->txrx->receiver = subghz_receiver_alloc_init(remote->txrx->environment);*/
+    //remote->txrx->worker = subghz_worker_alloc();
+
+    return remote;
+}
+
+void xremote_sg_remote_free(SubGhzRemote* remote) {
+    furi_string_free(remote->path);
+    furi_string_free(remote->name);
+
+    // TXRX
+    subghz_receiver_free(remote->txrx->receiver);
+    subghz_environment_free(remote->txrx->environment);
+    flipper_format_free(remote->txrx->fff_data);
+    furi_string_free(remote->txrx->preset->name);
+    free(remote->txrx->preset);
+    free(remote->txrx);
+
+    free(remote);
+}
+
+bool xremtoe_sg_set_preset(SubGhzRemote* remote, const char* preset) {
+    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
+        furi_string_set(remote->txrx->preset->name, "AM270");
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
+        furi_string_set(remote->txrx->preset->name, "AM650");
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
+        furi_string_set(remote->txrx->preset->name, "FM238");
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
+        furi_string_set(remote->txrx->preset->name, "FM476");
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
+        furi_string_set(remote->txrx->preset->name, "CUSTOM");
+    } else {
+        FURI_LOG_E(TAG, "Unknown preset");
+        return false;
+    }
+    return true;
+}
+
+uint32_t xremote_sg_remote_get_frequency(SubGhzRemote* remote) {
+    return remote->txrx->preset->frequency;
+}
+
+const char* xremote_sg_remote_get_preset(SubGhzRemote* remote) {
+    return furi_string_get_cstr(remote->txrx->preset->name);
+}
+
+bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
+
+    FuriString* buf;
+    buf = furi_string_alloc();
+    uint32_t temp_data32;
+
+    FURI_LOG_I(TAG, "loading SG Remote: \'%s\'", furi_string_get_cstr(path));
+    bool success = false;
+
+    do {
+        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
+        const char* fullPath = furi_string_get_cstr(path);
+        char* fileName = strrchr(fullPath, '/') + 1;
+        char* dotPosition = strrchr(fileName, '.');
+        if (dotPosition != NULL) {  // check if there is a dot in the file name
+            *dotPosition = '\0';     // set the dot position to NULL character to truncate the string
+        }
+        //remote->name = fileName;
+        furi_string_set_str(remote->name, fileName);
+        uint32_t version;
+        if(!flipper_format_read_header(ff, buf, &version)) break;
+        if(!furi_string_equal(buf, "Flipper SubGhz RAW File") || (version != 1)) break;
+
+        if(!flipper_format_read_uint32(ff, "Frequency", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing Frequency");
+            break;
+        }
+        remote->frequency = temp_data32;
+
+        if(!flipper_format_read_string(ff, "Preset", buf)) {
+            FURI_LOG_E(TAG, "Missing Preset");
+            break;
+        }
+        if(!xremtoe_sg_set_preset(remote, furi_string_get_cstr(buf))) {
+            break;
+        }
+
+        if(!strcmp(furi_string_get_cstr(buf), "FuriHalSubGhzPresetCustom")) {
+            //Todo add Custom_preset_module
+            //delete preset if it already exists
+            subghz_setting_delete_custom_preset(
+                remote->setting, furi_string_get_cstr(remote->txrx->preset->name));
+            //load custom preset from file
+            if(!subghz_setting_load_custom_preset(
+                   remote->setting,
+                   furi_string_get_cstr(remote->txrx->preset->name),
+                   ff)) {
+                FURI_LOG_E(TAG, "Missing Custom preset");
+                break;
+            }
+        }
+
+        if(!flipper_format_read_string(ff, "Protocol", buf)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+
+        if(!strcmp(furi_string_get_cstr(buf), "RAW")) {
+            subghz_protocol_raw_gen_fff_data(remote->txrx->fff_data, furi_string_get_cstr(path), subghz_txrx_radio_device_get_name(remote->txrx));
+        } else {
+            stream_copy_full(
+                flipper_format_get_raw_stream(ff),
+                flipper_format_get_raw_stream(remote->txrx->fff_data));
+        }
+
+        /*remote->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name(
+            remote->txrx->receiver, furi_string_get_cstr(buf));*/
+        if(remote->txrx->decoder_result) {
+            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
+                remote->txrx->decoder_result, remote->txrx->fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                //load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
+                success = false;
+                break;
+            }
+        } else {
+            FURI_LOG_E(TAG, "Protocol not found");
+            break;
+        }
+
+        success = true;
+    } while(false);
+
+    furi_string_free(buf);
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 21 - 0
models/subghz/xremote_sg_remote.h

@@ -0,0 +1,21 @@
+#pragma once
+
+//#include "../../xremote_i.h"
+#include "subghz_i.h"
+//#include <lib/subghz/protocols/protocol_items.h>
+
+//extern const SubGhzProtocolRegistry subghz_protocol_registry;
+
+typedef struct SubGhzRemote SubGhzRemote;
+
+const char* xremote_sg_remote_get_name(SubGhzRemote* remote);
+
+SubGhzRemote* xremote_sg_remote_alloc();
+
+void xremote_sg_remote_free(SubGhzRemote* remote);
+
+bool xremote_sg_remote_load(SubGhzRemote* remote, FuriString* path);
+
+uint32_t xremote_sg_remote_get_frequency(SubGhzRemote* remote);
+
+const char* xremote_sg_remote_get_preset(SubGhzRemote* remote);

+ 30 - 0
scenes/xremote_scene.c

@@ -0,0 +1,30 @@
+#include "xremote_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const xremote_on_enter_handlers[])(void*) = {
+#include "xremote_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 xremote_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "xremote_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 xremote_on_exit_handlers[])(void* context) = {
+#include "xremote_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers xremote_scene_handlers = {
+    .on_enter_handlers = xremote_on_enter_handlers,
+    .on_event_handlers = xremote_on_event_handlers,
+    .on_exit_handlers = xremote_on_exit_handlers,
+    .scene_num = XRemoteSceneNum,
+};

+ 29 - 0
scenes/xremote_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) XRemoteScene##id,
+typedef enum {
+#include "xremote_scene_config.h"
+    XRemoteSceneNum,
+} XRemoteScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers xremote_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "xremote_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 "xremote_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 "xremote_scene_config.h"
+#undef ADD_SCENE

+ 17 - 0
scenes/xremote_scene_config.h

@@ -0,0 +1,17 @@
+ADD_SCENE(xremote, infoscreen, Infoscreen)
+ADD_SCENE(xremote, menu, Menu)
+ADD_SCENE(xremote, create, Create)
+ADD_SCENE(xremote, create_add, CreateAdd)
+ADD_SCENE(xremote, edit_item, EditItem)
+ADD_SCENE(xremote, settings, Settings)
+ADD_SCENE(xremote, wip, Wip)
+ADD_SCENE(xremote, ir_list, IrList)
+ADD_SCENE(xremote, xr_list, XrList)
+ADD_SCENE(xremote, xr_list_edit, XrListEdit)
+ADD_SCENE(xremote, xr_list_edit_item, XrListEditItem)
+ADD_SCENE(xremote, sg_list, SgList)
+ADD_SCENE(xremote, ir_remote, IrRemote)
+ADD_SCENE(xremote, save_remote, SaveRemote)
+ADD_SCENE(xremote, save_remote_item, SaveRemoteItem)
+ADD_SCENE(xremote, transmit, Transmit)
+ADD_SCENE(xremote, pause_set, PauseSet)

+ 130 - 0
scenes/xremote_scene_create.c

@@ -0,0 +1,130 @@
+#include "../xremote.h"
+#include "../helpers/xremote_custom_event.h"
+
+typedef enum {
+    ButtonIndexPlus = -2,
+    ButtonIndexSave = -1,
+    ButtonIndexNA = 0,
+} ButtonIndex;
+
+static void xremote_create_callback(void* context, int32_t index, InputType type) {
+    XRemote* app = context;
+    
+    uint16_t custom_type;
+    if(type == InputTypePress) {
+        custom_type = XRemoteCustomEventMenuSelected;
+    } else if(type == InputTypeRelease) {
+        custom_type = XRemoteCustomEventMenuVoid;
+    } else if(type == InputTypeShort) {
+        //somehow ButtonMenuItemTypeCommon uses InputTypeShort
+        custom_type = XRemoteCustomEventMenuSelected;
+    } else {
+        furi_crash("Unexpected Input Type");
+    }
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, xremote_custom_menu_event_pack(custom_type, index));
+}
+
+void xremote_scene_create_on_enter(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    ButtonMenu* button_menu = app->button_menu_create;
+    //SceneManager* scene_manager = app->scene_manager;
+
+    size_t item_count = cross_remote_get_item_count(app->cross_remote);
+    for(size_t i = 0; i < item_count; ++i) {
+        CrossRemoteItem* item = cross_remote_get_item(app->cross_remote, i);
+        button_menu_add_item(
+            button_menu,
+            xremote_remote_item_get_name(item),
+            i,
+            xremote_create_callback,
+            ButtonMenuItemTypeCommon,
+            context);
+    }
+
+    button_menu_add_item(
+        button_menu,
+        "+",
+        ButtonIndexPlus,
+        xremote_create_callback,
+        ButtonMenuItemTypeControl,
+        context);
+
+    button_menu_add_item(
+        button_menu,
+        "Save",
+        ButtonIndexSave,
+        xremote_create_callback,
+        ButtonMenuItemTypeControl,
+        context);
+
+    button_menu_set_header(button_menu, "Add Cmd");
+    const int16_t button_index =
+        (signed)scene_manager_get_scene_state(app->scene_manager, XRemoteViewIdCreate);
+    button_menu_set_selected_item(button_menu, button_index);
+    scene_manager_set_scene_state(app->scene_manager, XRemoteSceneCreate, ButtonIndexNA);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdCreate);
+}
+
+bool xremote_scene_create_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu);
+        consumed = true;
+        /*if(is_transmitter_idle) {
+            const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
+            consumed = scene_manager_search_and_switch_to_previous_scene_one_of(
+                scene_manager, possible_scenes, COUNT_OF(possible_scenes));
+        } else {
+            consumed = true;
+        }*/
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        const uint16_t custom_type = xremote_custom_menu_event_get_type(event.event);
+        const int16_t button_index = xremote_custom_menu_event_get_value(event.event);
+        scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneCreate, (unsigned)button_index);
+        if (custom_type == XRemoteCustomEventMenuSelected && button_index < 0) {
+            //scene_manager_set_scene_state(
+            //    app->scene_manager, XRemoteSceneCreate, (unsigned)button_index);
+            if(button_index == ButtonIndexPlus) {
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneCreateAdd);
+                consumed = true;
+            } else if(button_index == ButtonIndexSave) {
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneSaveRemote);
+            }
+        } else if(custom_type == XRemoteCustomEventMenuSelected) {
+            app->edit_item = button_index;
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneEditItem);
+        }
+        /*switch(event.event) {
+            case XRemoteCustomEventCreateLeft:
+            case XRemoteCustomEventCreateRight:
+                break;
+            case XRemoteCustomEventCreateUp:
+            case XRemoteCustomEventCreateDown:
+                break;
+            case XRemoteCustomEventCreateBack:
+                notification_message(app->notification, &sequence_reset_red);
+                notification_message(app->notification, &sequence_reset_green);
+                notification_message(app->notification, &sequence_reset_blue);
+                if(!scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, XRemoteSceneMenu)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }*/
+    }
+    
+    return consumed;
+}
+
+void xremote_scene_create_on_exit(void* context) {
+    XRemote* app = context;
+    button_menu_reset(app->button_menu_create);
+}

+ 97 - 0
scenes/xremote_scene_create_add.c

@@ -0,0 +1,97 @@
+#include "../xremote.h"
+#include "../helpers/xremote_custom_event.h"
+
+typedef enum {
+    ButtonIndexIr = -3,
+    ButtonIndexSubghz = -2,
+    ButtonIndexPause = -1,
+    ButtonIndexNA = 0,
+} ButtonIndex;
+
+static void xremote_create_add_callback(void* context, int32_t index, InputType type) {
+    XRemote* app = context;
+    
+    uint16_t custom_type;
+    if(type == InputTypePress) {
+        custom_type = XRemoteCustomEventMenuVoid;
+    } else if(type == InputTypeRelease) {
+        custom_type = XRemoteCustomEventMenuAddSelected;
+    } else if(type == InputTypeShort) {
+        custom_type = XRemoteCustomEventMenuVoid;
+    } else {
+        furi_crash("Unexpected Input Type");
+    }
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, xremote_custom_menu_event_pack(custom_type, index));
+}
+
+void xremote_scene_create_add_on_enter(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    ButtonMenu* button_menu = app->button_menu_create_add;
+    SceneManager* scene_manager = app->scene_manager;
+
+    button_menu_add_item(
+        button_menu,
+        "Infrared",
+        ButtonIndexIr,
+        xremote_create_add_callback,
+        ButtonMenuItemTypeCommon,
+        context);
+
+    button_menu_add_item(
+        button_menu,
+        "SubGhz",
+        ButtonIndexSubghz,
+        xremote_create_add_callback,
+        ButtonMenuItemTypeCommon,
+        context);
+
+    button_menu_add_item(
+        button_menu,
+        "Pause",
+        ButtonIndexPause,
+        xremote_create_add_callback,
+        ButtonMenuItemTypeCommon,
+        context);
+
+    button_menu_set_header(button_menu, "Choose Type");
+    const int16_t button_index =
+        (signed)scene_manager_get_scene_state(scene_manager, XRemoteViewIdCreateAdd);
+    button_menu_set_selected_item(button_menu, button_index);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdCreateAdd);
+}
+
+bool xremote_scene_create_add_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        const uint16_t custom_type = xremote_custom_menu_event_get_type(event.event);
+        const int16_t button_index = xremote_custom_menu_event_get_value(event.event);
+        if (custom_type == XRemoteCustomEventMenuAddSelected) {
+            furi_assert(button_index < 0);
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneCreate, (unsigned)button_index);
+            if(button_index == ButtonIndexIr) {
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneIrList);
+            }
+            if(button_index == ButtonIndexSubghz) {
+                //scene_manager_next_scene(app->scene_manager, XRemoteSceneSgList);
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+            }
+            if(button_index == ButtonIndexPause) {
+                scene_manager_next_scene(app->scene_manager, XRemoteScenePauseSet);
+            }
+            consumed = true;
+        }
+    }
+    
+    return consumed;
+}
+
+void xremote_scene_create_add_on_exit(void* context) {
+    XRemote* app = context;
+    button_menu_reset(app->button_menu_create_add);
+}

+ 50 - 0
scenes/xremote_scene_edit_item.c

@@ -0,0 +1,50 @@
+#include "../xremote.h"
+#include "../models/cross/xremote_remote.h"
+
+enum SubmenuIndexEdit {
+    SubmenuIndexRename = 10,
+    SubmenuIndexDelete,
+};
+
+void xremote_scene_edit_item_submenu_callback(void* context, uint32_t index) {
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void xremote_scene_edit_item_on_enter(void* context)
+{
+    XRemote* app = context;
+    submenu_add_item(app->editmenu, "Rename", SubmenuIndexRename, xremote_scene_edit_item_submenu_callback, app);
+    submenu_add_item(app->editmenu, "Delete", SubmenuIndexDelete, xremote_scene_edit_item_submenu_callback, app);
+
+    submenu_set_selected_item(app->editmenu, scene_manager_get_scene_state(app->scene_manager, XRemoteSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdEditItem);
+}
+
+bool xremote_scene_edit_item_on_event(void* context, SceneManagerEvent event)
+{
+    XRemote* app = context;
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexDelete) { 
+            cross_remote_remove_item(app->cross_remote, app->edit_item);
+        } else if(event.event == SubmenuIndexRename) {
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneSaveRemoteItem);
+            //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+            return 0;
+        }
+        scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
+    }
+    return 0;
+}
+
+
+void xremote_scene_edit_item_on_exit(void* context)
+{
+    XRemote* app = context;
+    submenu_reset(app->editmenu);
+}

+ 54 - 0
scenes/xremote_scene_infoscreen.c

@@ -0,0 +1,54 @@
+#include "../xremote.h"
+#include "../helpers/xremote_custom_event.h"
+#include "../views/xremote_infoscreen.h"
+
+void xremote_scene_infoscreen_callback(XRemoteCustomEvent event, void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void xremote_scene_infoscreen_on_enter(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    xremote_infoscreen_set_callback(app->xremote_infoscreen, xremote_scene_infoscreen_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdInfoscreen);
+}
+
+bool xremote_scene_infoscreen_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case XRemoteCustomEventInfoscreenLeft:
+            case XRemoteCustomEventInfoscreenRight:
+                break;
+            case XRemoteCustomEventInfoscreenUp:
+            case XRemoteCustomEventInfoscreenDown:
+                break;
+            case XRemoteCustomEventInfoscreenOk:
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu);
+                consumed = true;
+                break;
+            case XRemoteCustomEventInfoscreenBack:
+                notification_message(app->notification, &sequence_reset_red);
+                notification_message(app->notification, &sequence_reset_green);
+                notification_message(app->notification, &sequence_reset_blue);
+                if(!scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, XRemoteSceneInfoscreen)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+    
+    return consumed;
+}
+
+void xremote_scene_infoscreen_on_exit(void* context) {
+    XRemote* app = context;
+    UNUSED(app);
+}

+ 46 - 0
scenes/xremote_scene_ir_list.c

@@ -0,0 +1,46 @@
+#include "../xremote.h"
+
+void xremote_scene_ir_list_on_enter(void* context) {
+    XRemote* app = context;
+    
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px);
+    browser_options.base_path = INFRARED_APP_FOLDER;
+
+    FuriString* path;
+    path = furi_string_alloc();
+    furi_string_set(path, INFRARED_APP_FOLDER);
+    bool success = dialog_file_browser_show(
+        app->dialogs, app->file_path, path, &browser_options);
+    furi_string_free(path);
+
+    if(success) {
+        //Load Remote into buffer
+        view_set_orientation(view_stack_get_view(app->view_stack), ViewOrientationVertical);
+        view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdStack);
+
+        xremote_ir_remote_load(app->ir_remote_buffer, app->file_path);
+    }
+
+    if(success) {
+        //Load Remote Button View
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneCreateAdd);
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_next_scene(app->scene_manager, XRemoteSceneIrRemote);
+    } else {
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool xremote_scene_ir_list_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = true;
+
+    return consumed;
+}
+
+void xremote_scene_ir_list_on_exit(void* context) {
+    UNUSED(context);
+}

+ 78 - 0
scenes/xremote_scene_ir_remote.c

@@ -0,0 +1,78 @@
+#include "../xremote.h"
+
+typedef enum {
+    ButtonIndexPlus = -2,
+    ButtonIndexEdit = -1,
+    ButtonIndexNA = 0,
+} ButtonIndex;
+
+static void xremote_scene_ir_remote_button_menu_callback(void* context, int32_t index, InputType type) {
+    XRemote* app = context;
+
+    uint16_t custom_type;
+    if(type == InputTypePress) {
+        custom_type = XRemoteCustomEventMenuVoid;
+    } else if(type == InputTypeRelease) {
+        custom_type = XRemoteCustomEventMenuAddIrSelected;
+    } else if(type == InputTypeShort) {
+        custom_type = XRemoteCustomEventMenuVoid;
+    } else {
+        furi_crash("Unexpected IR input type");
+    }
+
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, xremote_custom_menu_event_pack(custom_type, index));
+}
+
+void xremote_scene_ir_remote_on_enter(void* context) {
+    XRemote* app = context;
+    ButtonMenu* button_menu = app->button_menu_ir;
+
+    size_t button_count = xremote_ir_remote_get_button_count(app->ir_remote_buffer);
+    for(size_t i = 0; i < button_count; i++) {
+        InfraredRemoteButton* button = xremote_ir_remote_get_button(app->ir_remote_buffer, i);
+        button_menu_add_item(
+            button_menu,
+            xremote_ir_remote_button_get_name(button),
+            i,
+            xremote_scene_ir_remote_button_menu_callback,
+            ButtonMenuItemTypeCommon,
+            context);
+    }
+
+    button_menu_set_header(button_menu, "Select Cmd");
+    const int16_t button_index = 
+        (signed)scene_manager_get_scene_state(app->scene_manager, XRemoteViewIdIrRemote);
+    button_menu_set_selected_item(button_menu, button_index);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdIrRemote);
+}
+
+bool xremote_scene_ir_remote_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        const uint16_t custom_type = xremote_custom_menu_event_get_type(event.event);
+        const int16_t button_index = xremote_custom_menu_event_get_value(event.event);
+        if (custom_type == XRemoteCustomEventMenuAddIrSelected) {
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneIrRemote, (unsigned)button_index);
+            
+            InfraredRemoteButton* ir_button = xremote_ir_remote_get_button(app->ir_remote_buffer, button_index);
+            const char* button_name = xremote_ir_remote_button_get_name(ir_button);
+            InfraredSignal* signal = xremote_ir_remote_button_get_signal(ir_button);
+
+            cross_remote_add_ir_item(app->cross_remote, button_name, signal);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
+            consumed = true;
+        }
+    }
+    
+    return consumed;
+}
+
+void xremote_scene_ir_remote_on_exit(void* context) {
+    XRemote* app = context;
+    button_menu_reset(app->button_menu_ir);
+}

+ 70 - 0
scenes/xremote_scene_menu.c

@@ -0,0 +1,70 @@
+#include "../xremote.h"
+
+enum SubmenuIndex {
+    SubmenuIndexCreate = 10,
+    SubmenuIndexLoad,
+    SubmenuIndexEdit,
+    SubmenuIndexSettings,
+    SubmenuIndexInfoscreen,
+};
+
+void xremote_scene_menu_submenu_callback(void* context, uint32_t index) {
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void xremote_scene_menu_on_enter(void* context) {
+    XRemote* app = context;
+
+    submenu_add_item(app->submenu, "New Command Chain", SubmenuIndexCreate, xremote_scene_menu_submenu_callback, app);
+    submenu_add_item(app->submenu, "Run Saved Command", SubmenuIndexLoad, xremote_scene_menu_submenu_callback, app);
+    submenu_add_item(app->submenu, "Edit / Delete Command", SubmenuIndexEdit, xremote_scene_menu_submenu_callback, app);
+    submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, xremote_scene_menu_submenu_callback, app);
+    submenu_add_item(app->submenu, "App Info", SubmenuIndexInfoscreen, xremote_scene_menu_submenu_callback, app);
+
+    submenu_set_selected_item(app->submenu, scene_manager_get_scene_state(app->scene_manager, XRemoteSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdMenu);
+}
+
+bool xremote_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexCreate) {
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneMenu, SubmenuIndexCreate);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
+            return true;
+        } else if (event.event == SubmenuIndexLoad) {
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneMenu, SubmenuIndexLoad);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneXrList);
+            return true;
+        } else if (event.event == SubmenuIndexSettings) {
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneMenu, SubmenuIndexSettings);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneSettings);
+            return true;
+        } else if (event.event == SubmenuIndexInfoscreen) {
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneMenu, SubmenuIndexInfoscreen);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneInfoscreen);
+            return true;
+        } else if (event.event == SubmenuIndexEdit) {
+            scene_manager_set_scene_state(
+                app->scene_manager, XRemoteSceneMenu, SubmenuIndexEdit);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneXrListEdit);
+        }
+    }
+    return false;
+}
+
+void xremote_scene_menu_on_exit(void* context) {
+    XRemote* app = context;
+    submenu_reset(app->submenu);
+}

+ 54 - 0
scenes/xremote_scene_pause_set.c

@@ -0,0 +1,54 @@
+#include "../xremote.h"
+#include "../views/xremote_pause_set.h"
+
+void xremote_scene_pause_set_callback(XRemoteCustomEvent event, void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void xremote_scene_pause_set_on_enter(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    xremote_pause_set_set_callback(app->xremote_pause_set, xremote_scene_pause_set_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdPauseSet);
+}
+
+bool xremote_scene_pause_set_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case XRemoteCustomEventInfoscreenLeft:
+            case XRemoteCustomEventInfoscreenRight:
+                break;
+            case XRemoteCustomEventInfoscreenUp:
+            case XRemoteCustomEventInfoscreenDown:
+                break;
+            case XRemoteCustomEventInfoscreenOk:
+                scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu);
+                consumed = true;
+                break;
+            case XRemoteCustomEventPauseSetBack:
+                if(!scene_manager_search_and_switch_to_previous_scene(
+                    app->scene_manager, XRemoteSceneCreateAdd)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+            case XRemoteCustomEventPauseSetOk:
+                //cross_remote_add_pause(app->cross_remote, time);
+                scene_manager_search_and_switch_to_previous_scene(app->scene_manager, XRemoteSceneCreate);
+                consumed = true;
+                break;
+        }
+    }
+    return consumed;
+}
+
+
+void xremote_scene_pause_set_on_exit(void* context) {
+    UNUSED(context);
+}

+ 74 - 0
scenes/xremote_scene_save_remote.c

@@ -0,0 +1,74 @@
+#include "../xremote.h"
+
+#include <string.h>
+#include <toolbox/path.h>
+
+void xremote_scene_save_remote_on_enter(void* context) {
+    XRemote* app = context;
+    CrossRemote* remote = app->cross_remote;
+    TextInput* text_input = app->text_input;
+    size_t enter_name_length = 0;
+
+    text_input_set_header_text(text_input, "Name the remote");
+    enter_name_length = XREMOTE_MAX_REMOTE_NAME_LENGTH;
+
+    FuriString* folder_path;
+    folder_path = furi_string_alloc();
+
+    //if(furi_string_end_with(app->))
+
+    //A lot missing here
+
+    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+        furi_string_get_cstr(folder_path),
+        XREMOTE_APP_EXTENSION,
+        cross_remote_get_name(remote));
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    furi_string_free(folder_path);
+
+    text_input_set_result_callback(
+        text_input,
+        xremote_text_input_callback,
+        context,
+        app->text_store[0],
+        enter_name_length,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdTextInput);
+}
+
+bool xremote_scene_save_remote_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    CrossRemote* remote = app->cross_remote;
+    SceneManager* scene_manager = app->scene_manager;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        bool success = false;
+
+        success = cross_remote_save_new(remote, app->text_store[0]);
+
+        if(success) {
+            scene_manager_next_scene(scene_manager, XRemoteSceneMenu);
+        } else {
+            scene_manager_search_and_switch_to_previous_scene(scene_manager, XRemoteSceneCreate);
+        }
+        consumed = true;
+    }
+    return consumed;
+}
+
+void xremote_scene_save_remote_on_exit(void* context) {
+    XRemote* app = context;
+    TextInput* text_input = app->text_input;
+
+    void* validator_context = text_input_get_validator_callback_context(text_input);
+    text_input_set_validator(text_input, NULL, NULL);
+
+    size_t enter_name_length = XREMOTE_MAX_REMOTE_NAME_LENGTH;
+    strncpy(app->text_store[0], "", enter_name_length);
+    if(validator_context) {
+        validator_is_file_free((ValidatorIsFile*)validator_context);
+    }
+}

+ 45 - 0
scenes/xremote_scene_save_remote_item.c

@@ -0,0 +1,45 @@
+#include "../xremote.h"
+#include "../models/cross/xremote_remote.h"
+
+#include <string.h>
+#include <toolbox/path.h>
+
+void xremote_scene_save_remote_item_on_enter(void* context) {
+    XRemote* app = context;
+    TextInput* text_input = app->text_input;
+
+    text_input_set_header_text(text_input, "Name the Sequence");
+    
+    size_t enter_name_length = XREMOTE_MAX_REMOTE_NAME_LENGTH;
+    CrossRemoteItem* item = cross_remote_get_item(app->cross_remote, app->edit_item);
+    strncpy(app->text_store[0], furi_string_get_cstr(item->name), enter_name_length);
+    text_input_set_result_callback(
+        text_input,
+        xremote_text_input_callback,
+        context,
+        app->text_store[0],
+        enter_name_length,
+        false);
+    
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdTextInput);
+}
+
+bool xremote_scene_save_remote_item_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    CrossRemote* remote = app->cross_remote;
+    SceneManager* scene_manager = app->scene_manager;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        cross_remote_rename_item(remote, app->edit_item, app->text_store[0]);
+        scene_manager_next_scene(scene_manager, XRemoteSceneCreate);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void xremote_scene_save_remote_item_on_exit(void* context) {
+    XRemote* app = context;
+    size_t enter_name_length = XREMOTE_MAX_REMOTE_NAME_LENGTH;
+    strncpy(app->text_store[0], "", enter_name_length);
+}

+ 147 - 0
scenes/xremote_scene_settings.c

@@ -0,0 +1,147 @@
+#include "../xremote.h"
+#include <lib/toolbox/value_index.h>
+
+enum SettingsIndex {
+    SettingsIndexHaptic = 10,
+    SettingsIndexValue1,
+    SettingsIndexValue2,
+};
+
+const char* const haptic_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t haptic_value[2] = {
+    XRemoteHapticOff,
+    XRemoteHapticOn,
+};
+
+const char* const speaker_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t speaker_value[2] = {
+    XRemoteSpeakerOff,
+    XRemoteSpeakerOn,
+};
+
+const char* const led_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t led_value[2] = {
+    XRemoteLedOff,
+    XRemoteLedOn,
+};
+
+const char* const settings_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t settings_value[2] = {
+    XRemoteSettingsOff,
+    XRemoteSettingsOn,
+};
+
+
+static void xremote_scene_settings_set_haptic(VariableItem* item) {
+    XRemote* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, haptic_text[index]);
+    app->haptic = haptic_value[index];
+}
+
+static void xremote_scene_settings_set_speaker(VariableItem* item) {
+    XRemote* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, speaker_text[index]);
+    app->speaker = speaker_value[index];
+}
+
+static void xremote_scene_settings_set_led(VariableItem* item) {
+    XRemote* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, led_text[index]);
+    app->led = led_value[index];
+}
+
+static void xremote_scene_settings_set_save_settings(VariableItem* item) {
+    XRemote* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, settings_text[index]);
+    app->save_settings = settings_value[index];
+}
+
+void xremote_scene_settings_submenu_callback(void* context, uint32_t index) {
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void xremote_scene_settings_on_enter(void* context) {
+    XRemote* app = context;
+    VariableItem* item;
+    uint8_t value_index;
+
+    // Vibro on/off
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Vibro/Haptic:",
+        2,
+        xremote_scene_settings_set_haptic,
+        app);
+    value_index = value_index_uint32(app->haptic, haptic_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, haptic_text[value_index]);
+
+    // Sound on/off
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Sound:",
+        2,
+        xremote_scene_settings_set_speaker,
+        app);
+    value_index = value_index_uint32(app->speaker, speaker_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, speaker_text[value_index]);
+
+    // LED Effects on/off
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "LED FX:",
+        2,
+        xremote_scene_settings_set_led,
+        app);
+    value_index = value_index_uint32(app->led, led_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, led_text[value_index]);
+
+    // Save Settings to File
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Save Settings",
+        2,
+        xremote_scene_settings_set_save_settings,
+        app);
+    value_index = value_index_uint32(app->save_settings, settings_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, settings_text[value_index]);
+    
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdSettings);
+}
+
+bool xremote_scene_settings_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        
+    }
+    return consumed;
+}
+
+void xremote_scene_settings_on_exit(void* context) {
+    XRemote* app = context;
+    variable_item_list_set_selected_item(app->variable_item_list, 0);
+    variable_item_list_reset(app->variable_item_list);
+}

+ 48 - 0
scenes/xremote_scene_sg_list.c

@@ -0,0 +1,48 @@
+#include "../xremote.h"
+
+void xremote_scene_sg_list_on_enter(void* context) {
+    XRemote* app = context;
+    
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sg_10px);
+    browser_options.base_path = SUBGHZ_APP_FOLDER;
+
+    FuriString* path;
+    path = furi_string_alloc();
+    furi_string_set(path, SUBGHZ_APP_FOLDER);
+    bool success = dialog_file_browser_show(
+        app->dialogs, app->file_path, path, &browser_options);
+    furi_string_free(path);
+
+    if(success) {
+        //Load Remote into buffer
+        view_set_orientation(view_stack_get_view(app->view_stack), ViewOrientationVertical);
+        view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdStack);
+
+        xremote_sg_remote_load(app->sg_remote_buffer, app->file_path);
+        //xremote_ir_remote_load(app->ir_remote_buffer, app->file_path);
+        cross_remote_add_subghz(app->cross_remote, app->sg_remote_buffer);
+    }
+
+    if(success) {
+        //Load Remote Button View
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneCreateAdd);
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneIrRemote);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool xremote_scene_sg_list_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = true;
+
+    return consumed;
+}
+
+void xremote_scene_sg_list_on_exit(void* context) {
+    UNUSED(context);
+}

+ 154 - 0
scenes/xremote_scene_transmit.c

@@ -0,0 +1,154 @@
+#include "../xremote.h"
+#include "../views/xremote_transmit.h"
+#include "../models/infrared/xremote_ir_signal.h"
+
+static const NotificationSequence* xremote_notification_sequences[] = {
+    &sequence_success,
+    &sequence_set_only_green_255,
+    &sequence_reset_green,
+    &sequence_solid_yellow,
+    &sequence_reset_rgb,
+    &sequence_blink_start_cyan,
+    &sequence_blink_start_magenta,
+    &sequence_blink_stop,
+    &sequence_blink_start_yellow,
+    &sequence_blink_stop,
+    &sequence_blink_start_blue,
+    &sequence_blink_stop,
+};
+
+void xremote_transmit_callback(XRemoteCustomEvent event, void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void xremote_scene_ir_notification_message(XRemote* app, uint32_t message) {
+    if (app->led == 1) {
+        notification_message(app->notification, xremote_notification_sequences[message]);
+    }
+}
+
+bool xremote_scene_ir_signal_is_raw(InfraredSignal* signal) {
+    if (signal->is_raw) {
+        return true;
+    }
+    return false;
+}
+
+void xremote_scene_transmit_stop_ir_signal(XRemote* app) {
+    if(!app->transmitting) {
+        return;
+    }
+    app->transmitting = false;
+    infrared_worker_tx_stop(app->ir_worker);
+    infrared_worker_tx_set_get_signal_callback(app->ir_worker, NULL, NULL);
+    xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
+}
+
+void xremote_scene_transmit_send_ir_signal(XRemote* app, CrossRemoteItem* item) {
+    InfraredSignal* signal = xremote_remote_item_get_ir_signal(item);
+    dolphin_deed(DolphinDeedIrSend);
+    xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStartSend);
+    if (xremote_scene_ir_signal_is_raw(signal)) {
+        InfraredRawSignal* raw = xremote_ir_signal_get_raw_signal(signal);
+        infrared_worker_set_raw_signal(app->ir_worker, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle);
+    } else {
+        InfraredMessage* message = xremote_ir_signal_get_message(signal);
+        infrared_worker_set_decoded_signal(app->ir_worker, message);
+    }
+    infrared_worker_tx_set_get_signal_callback(
+        app->ir_worker, infrared_worker_tx_get_signal_steady_callback, app);
+    infrared_worker_tx_start(app->ir_worker);
+    app->transmitting = true;    
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
+    xremote_scene_transmit_stop_ir_signal(app);
+}
+
+void xremote_scene_transmit_send_pause(XRemote* app, CrossRemoteItem* item) {
+    app->transmitting = true;
+    xremote_scene_ir_notification_message(app, PauseNotificationMessageBlinkStartSend);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, item->time * 1000);
+    app->transmitting = false;
+    xremote_scene_ir_notification_message(app, PauseNotificationMessageBlinkStop);
+}
+
+void xremote_scene_transmit_send_subghz(XRemote* app, CrossRemoteItem* item) {
+    UNUSED(item);
+    app->transmitting = true;
+    xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStartSend);
+    // ADD SEND METHOD HERE 
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 2000); //Remove later
+    app->transmitting = false;
+    xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
+}
+
+void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
+    furi_assert(context);
+    XRemote* app = context;
+    CrossRemote* remote = app->cross_remote;
+
+    if(app->transmitting) {
+        return;
+    }
+    
+    xremote_transmit_model_set_name(app->xremote_transmit, xremote_remote_item_get_name(item));
+    xremote_transmit_model_set_type(app->xremote_transmit, item->type);
+    if(item->type == XRemoteRemoteItemTypeInfrared) {
+        xremote_scene_transmit_send_ir_signal(app, item);
+    } else if(item->type == XRemoteRemoteItemTypePause) { 
+        xremote_scene_transmit_send_pause(app, item);
+    } else if(item->type == XRemoteRemoteItemTypeSubGhz) {
+        xremote_scene_transmit_send_subghz(app, item);
+    }
+
+    cross_remote_set_transmitting(remote, XRemoteTransmittingStop);
+}
+
+void xremote_scene_transmit_run_remote(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    CrossRemote* remote = app->cross_remote;
+
+    size_t item_count = cross_remote_get_item_count(remote);
+    for(size_t i = 0; i < item_count;) {
+        if (cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
+            cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
+            CrossRemoteItem* item = cross_remote_get_item(remote, i);
+            xremote_scene_transmit_send_signal(app, item);
+            //furi_thread_flags_wait(0, FuriFlagWaitAny, 2000);
+            xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStartSend);
+        } else if(cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
+            i++;
+            cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+        } 
+    }
+    xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
+
+    //scene_manager_next_scene(app->scene_manager, XRemoteSceneXrList);
+    scene_manager_previous_scene(app->scene_manager);
+    //xremote_transmit_model_set_name(app->xremote_transmit, cross_remote_get_name(remote));
+}
+
+void xremote_scene_transmit_on_enter(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    xremote_transmit_set_callback(app->xremote_transmit, xremote_transmit_callback, app);
+    
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdTransmit);
+    xremote_scene_transmit_run_remote(app);
+}
+
+bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+
+    UNUSED(app);
+    UNUSED(event);
+    bool consumed = false;
+    return consumed;
+}
+
+void xremote_scene_transmit_on_exit(void* context) {
+    XRemote* app = context;
+    UNUSED(app);
+}

+ 39 - 0
scenes/xremote_scene_wip.c

@@ -0,0 +1,39 @@
+#include "../xremote.h"
+
+void xremote_scene_wip_on_enter(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    Popup* popup = app->popup;
+    
+    //popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
+    popup_set_header(popup, "SubGhz coming soon", 10, 19, AlignLeft, AlignBottom);
+    popup_set_text(popup, "Check back later", 10, 29, AlignLeft, AlignBottom);
+    popup_set_text(popup, "Press back long", 10, 39, AlignLeft, AlignBottom);
+
+    popup_set_callback(popup, xremote_popup_closed_callback);
+    popup_set_context(popup, context);
+    popup_set_timeout(popup, 100);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdWip);
+}
+
+bool xremote_scene_wip_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        if (event.event == XRemoteCustomEventTypePopupClosed) {
+
+        }
+        consumed = true;
+    }
+    
+    return consumed;
+}
+
+void xremote_scene_wip_on_exit(void* context) {
+    XRemote* app = context;
+    UNUSED(app);
+}

+ 44 - 0
scenes/xremote_scene_xr_list.c

@@ -0,0 +1,44 @@
+#include "../xremote.h"
+
+void xremote_scene_xr_list_on_enter(void* context) {
+    XRemote* app = context;
+    
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, XREMOTE_APP_EXTENSION, &I_xr_10px);
+    browser_options.base_path = XREMOTE_APP_FOLDER;
+
+    FuriString* path;
+    path = furi_string_alloc();
+    furi_string_set(path, XREMOTE_APP_FOLDER);
+
+    bool success = dialog_file_browser_show(
+        //app->dialogs, app->file_path, app->file_path, &browser_options);
+        app->dialogs, app->file_path, path, &browser_options);
+    furi_string_free(path);
+
+    if(success) {
+        //Load Remote into buffer
+        success = cross_remote_load(app->cross_remote, app->file_path);
+    }
+
+    if(success) {
+        //Load Remote Button View
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_next_scene(app->scene_manager, XRemoteSceneTransmit);
+    } else {
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool xremote_scene_xr_list_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = true;
+
+    return consumed;
+}
+
+void xremote_scene_xr_list_on_exit(void* context) {
+    UNUSED(context);
+}

+ 43 - 0
scenes/xremote_scene_xr_list_edit.c

@@ -0,0 +1,43 @@
+#include "../xremote.h"
+
+void xremote_scene_xr_list_edit_on_enter(void* context) {
+    XRemote* app = context;
+    
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, XREMOTE_APP_EXTENSION, &I_xr_10px);
+    browser_options.base_path = XREMOTE_APP_FOLDER;
+
+    FuriString* path;
+    path = furi_string_alloc();
+    furi_string_set(path, XREMOTE_APP_FOLDER);
+
+    bool success = dialog_file_browser_show(
+        app->dialogs, app->file_path, path, &browser_options);
+    furi_string_free(path);
+
+    if(success) {
+        //Load Remote into buffer
+        success = cross_remote_load(app->cross_remote, app->file_path);
+    }
+
+    if(success) {
+        //Load Remote Button View
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_next_scene(app->scene_manager, XRemoteSceneXrListEditItem);
+    } else {
+        //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool xremote_scene_xr_list_edit_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = true;
+
+    return consumed;
+}
+
+void xremote_scene_xr_list_edit_on_exit(void* context) {
+    UNUSED(context);
+}

+ 51 - 0
scenes/xremote_scene_xr_list_edit_item.c

@@ -0,0 +1,51 @@
+#include "../xremote.h"
+
+enum SubmenuIndexEdit {
+    SubmenuIndexRename = 10,
+    SubmenuIndexEdit,
+    SubmenuIndexDelete,
+};
+
+void xremote_scene_xr_list_edit_item_submenu_callback(void* context, uint32_t index) {
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void xremote_scene_xr_list_edit_item_on_enter(void* context) {
+    XRemote* app = context;
+    //submenu_add_item(app->editmenu, "Rename", SubmenuIndexRename, xremote_scene_xr_list_edit_item_submenu_callback, app);
+    submenu_add_item(app->editmenu, "Edit", SubmenuIndexEdit, xremote_scene_xr_list_edit_item_submenu_callback, app);
+    submenu_add_item(app->editmenu, "Delete", SubmenuIndexDelete, xremote_scene_xr_list_edit_item_submenu_callback, app);
+
+    submenu_set_selected_item(app->editmenu, scene_manager_get_scene_state(app->scene_manager, XRemoteSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdEditItem);
+}
+
+bool xremote_scene_xr_list_edit_item_on_event(void* context, SceneManagerEvent event) {
+    XRemote* app = context;
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexDelete) { 
+            cross_remote_delete(app->cross_remote);
+        } else if(event.event == SubmenuIndexEdit) {
+            strncpy(app->text_store[0], cross_remote_get_name(app->cross_remote), XREMOTE_MAX_REMOTE_NAME_LENGTH);
+            scene_manager_next_scene(app->scene_manager, XRemoteSceneCreate);
+            return 0;
+        } else if(event.event == SubmenuIndexRename) {
+            //scene_manager_next_scene(app->scene_manager, XRemoteSceneSaveRemoteItem);
+            //scene_manager_next_scene(app->scene_manager, XRemoteSceneWip);
+            return 0;
+        }
+        scene_manager_previous_scene(app->scene_manager);
+    }
+    return 0;
+}
+
+void xremote_scene_xr_list_edit_item_on_exit(void* context) {
+    XRemote* app = context;
+    submenu_reset(app->editmenu);
+}

BIN
screenshots/xremote_1.png


BIN
screenshots/xremote_2.png


BIN
screenshots/xremote_3.png


BIN
screenshots/xremote_4.png


BIN
screenshots/xremote_5.png


BIN
screenshots/xremote_6.png


+ 134 - 0
views/xremote_infoscreen.c

@@ -0,0 +1,134 @@
+#include "xremote_infoscreen.h"
+#include "../xremote.h"
+
+struct XRemoteInfoscreen {
+    View* view;
+    XRemoteInfoscreenCallback callback;
+    void* context;
+};
+
+
+typedef struct {
+    int some_value;
+} XRemoteInfoscreenModel;
+
+void xremote_infoscreen_set_callback(
+    XRemoteInfoscreen* instance,
+    XRemoteInfoscreenCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void xremote_infoscreen_draw(Canvas* canvas, XRemoteInfoscreenModel* model) {
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Cross Remote"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Chain IR and SubGhz"); 
+    canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Commands");
+    elements_button_center(canvas, "Back"); 
+}
+
+static void xremote_infoscreen_model_init(XRemoteInfoscreenModel* const model) {
+    model->some_value = 1;
+}
+
+bool xremote_infoscreen_input(InputEvent* event, void* context) {
+    furi_assert(context); 
+    XRemoteInfoscreen* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                with_view_model(
+                    instance->view,
+                    XRemoteInfoscreenModel * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(XRemoteCustomEventInfoscreenBack, instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyLeft:
+            case InputKeyRight:
+            case InputKeyUp:
+            case InputKeyDown:
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    XRemoteInfoscreenModel* model,
+                    {
+                        UNUSED(model);
+                        instance->callback(XRemoteCustomEventInfoscreenOk, instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyMAX:
+                break;
+        }
+    }
+    return true;
+}
+
+void xremote_infoscreen_exit(void* context) {
+    furi_assert(context);
+}
+
+void xremote_infoscreen_enter(void* context) {
+    furi_assert(context);
+    XRemoteInfoscreen* instance = (XRemoteInfoscreen*)context;
+    with_view_model(
+        instance->view,
+        XRemoteInfoscreenModel * model,
+        {
+            xremote_infoscreen_model_init(model);
+        },
+        true
+    );
+}
+
+XRemoteInfoscreen* xremote_infoscreen_alloc() {
+    XRemoteInfoscreen* instance = malloc(sizeof(XRemoteInfoscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(XRemoteInfoscreenModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)xremote_infoscreen_draw);
+    view_set_input_callback(instance->view, xremote_infoscreen_input);
+    //view_set_enter_callback(instance->view, xremote_infoscreen_enter);
+    //view_set_exit_callback(instance->view, xremote_infoscreen_exit);
+
+    with_view_model(
+        instance->view,
+        XRemoteInfoscreenModel * model,
+        {
+            xremote_infoscreen_model_init(model);
+        },
+        true
+    );
+    
+    return instance;
+}
+
+void xremote_infoscreen_free(XRemoteInfoscreen* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        XRemoteInfoscreenModel * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* xremote_infoscreen_get_view(XRemoteInfoscreen* instance) {
+    furi_assert(instance);
+    return instance->view;
+}
+

+ 20 - 0
views/xremote_infoscreen.h

@@ -0,0 +1,20 @@
+#pragma once
+
+//#include "../xremote.h"
+#include <gui/view.h>
+#include "../helpers/xremote_custom_event.h"
+
+typedef struct XRemoteInfoscreen XRemoteInfoscreen;
+
+typedef void (*XRemoteInfoscreenCallback)(XRemoteCustomEvent event, void* context);
+
+void xremote_infoscreen_set_callback(
+    XRemoteInfoscreen* xremote_infoscreen,
+    XRemoteInfoscreenCallback callback,
+    void* context);
+
+View* xremote_infoscreen_get_view(XRemoteInfoscreen* instance);
+
+XRemoteInfoscreen* xremote_infoscreen_alloc();
+
+void xremote_infoscreen_free(XRemoteInfoscreen* instance);

+ 136 - 0
views/xremote_pause_set.c

@@ -0,0 +1,136 @@
+#include "xremote_pause_set.h"
+
+struct XRemotePauseSet {
+    View* view;
+    XRemotePauseSetCallback callback;
+    void* context;
+};
+
+typedef struct {
+    int type;
+    const char* name;
+    int time;
+} XRemotePauseSetModel;
+
+static void xremote_pause_set_model_init(XRemotePauseSetModel* const model) {
+    model->type = XRemoteRemoteItemTypePause;
+    model->time = 1;
+}
+
+void xremote_pause_set_set_callback(
+    XRemotePauseSet* instance,
+    XRemotePauseSetCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void xremote_pause_set_draw(Canvas* canvas, XRemotePauseSetModel* model) {
+    char seconds[14];
+    snprintf(seconds, SECONDS_LENGHT, SECONDS_FORMAT, model->time);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Pause duration"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_icon(canvas, 59, 22, &I_ButtonUp_10x5);
+    canvas_draw_icon(canvas, 59, 42, &I_ButtonDown_10x5);
+    canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, seconds); 
+    elements_button_center(canvas, "Add"); 
+}
+
+bool xremote_pause_set_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    XRemotePauseSet* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                instance->callback(XRemoteCustomEventPauseSetBack, instance->context);
+                break;
+            case InputKeyLeft:
+            case InputKeyRight:
+                break;
+            case InputKeyUp:
+                with_view_model(
+                    instance->view,
+                    XRemotePauseSetModel* model,
+                    {
+                        model->time++;
+                        if (model->time > 9) {
+                            model->time = 0;
+                        }
+                    },
+                    true);
+                break;
+            case InputKeyDown:
+                with_view_model(
+                    instance->view,
+                    XRemotePauseSetModel* model,
+                    {
+                        model->time--;
+                        if (model->time < 0) {
+                            model->time = 9;
+                        }
+                    },
+                    true);
+                break;
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    XRemotePauseSetModel * model,
+                    {
+                        XRemote* app = instance->context;
+                        cross_remote_add_pause(app->cross_remote, model->time);
+                    },
+                    true
+                );
+
+                instance->callback(XRemoteCustomEventPauseSetOk, instance->context);
+                break;
+            case InputKeyMAX:
+                break;
+        }
+    }
+    return true;
+}
+
+XRemotePauseSet* xremote_pause_set_alloc() {
+    XRemotePauseSet* instance = malloc(sizeof(XRemotePauseSet));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(XRemotePauseSetModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)xremote_pause_set_draw);
+    view_set_enter_callback(instance->view, xremote_pause_set_enter);
+    view_set_input_callback(instance->view, xremote_pause_set_input);
+    
+    with_view_model(
+        instance->view,
+        XRemotePauseSetModel * model,
+        {
+            xremote_pause_set_model_init(model);
+        },
+        true
+    );
+
+    return instance;
+}
+
+void xremote_pause_set_enter(void* context) {
+    furi_assert(context);
+    XRemotePauseSet* instance = (XRemotePauseSet*) context;
+    with_view_model(
+        instance->view,
+        XRemotePauseSetModel * model,
+        {
+            xremote_pause_set_model_init(model);
+        },
+        true
+    );
+}
+
+View* xremote_pause_set_get_view(XRemotePauseSet* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 23 - 0
views/xremote_pause_set.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include "../xremote.h"
+//#include <gui/view.h>
+#include "../helpers/xremote_custom_event.h"
+
+#define SECONDS_LENGHT 3
+#define SECONDS_FORMAT "%ds"
+
+typedef struct XRemotePauseSet XRemotePauseSet;
+
+typedef void (*XRemotePauseSetCallback)(XRemoteCustomEvent event, void* context);
+
+void xremote_pause_set_set_callback(
+    XRemotePauseSet* instance,
+    XRemotePauseSetCallback callback,
+    void* context);
+
+XRemotePauseSet* xremote_pause_set_alloc();
+
+void xremote_pause_set_enter(void* context);
+
+View* xremote_pause_set_get_view(XRemotePauseSet* instance);

+ 151 - 0
views/xremote_transmit.c

@@ -0,0 +1,151 @@
+#include "xremote_transmit.h"
+
+struct XRemoteTransmit {
+    View* view;
+    XRemoteTransmitCallback callback;
+    void* context;
+};
+
+typedef struct {
+    int type;
+    const char* name;
+    int time;
+} XRemoteTransmitModel;
+
+static void xremote_transmit_model_init(XRemoteTransmitModel* const model) {
+    model->type = XRemoteRemoteItemTypeInfrared;
+    model->time = 1;
+}
+
+void xremote_transmit_model_set_name(XRemoteTransmit* instance, const char* name) {
+    furi_assert(instance);
+    XRemoteTransmitModel* model = view_get_model(instance->view);
+    model->name = name;
+    view_commit_model(instance->view, false);
+}
+
+void xremote_transmit_model_set_type(XRemoteTransmit* instance, int type) {
+    furi_assert(instance);
+    XRemoteTransmitModel* model = view_get_model(instance->view);
+    model->time = 1;
+    model->type = type;
+    view_commit_model(instance->view, false);
+}
+
+void xremote_transmit_set_callback(
+    XRemoteTransmit* instance,
+    XRemoteTransmitCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void xremote_transmit_draw_ir(Canvas* canvas, XRemoteTransmitModel* model) {
+    model->time++;
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_icon(canvas, 0, 0, &I_ir_transmit_128x64);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Sending"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "Infrared"); 
+    canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
+    
+    char temp_str[18];
+    snprintf(temp_str, 18, "%u", model->time);
+    canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+}
+
+void xremote_transmit_draw_pause(Canvas* canvas, XRemoteTransmitModel* model) {
+    model->time++;
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_icon(canvas, 0, 0, &I_pause_128x64);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Waiting"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "Sequence"); 
+    canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
+    
+    char temp_str[18];
+    snprintf(temp_str, 18, "%u", model->time);
+    canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+}
+
+void xremote_transmit_draw_subghz(Canvas* canvas, XRemoteTransmitModel* model) {
+    model->time++;
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_icon(canvas, 0, 0, &I_sg_transmit_128x64);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Sending"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "SubGhz"); 
+    canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
+    
+    char temp_str[18];
+    snprintf(temp_str, 18, "%u", model->time);
+    canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+}
+
+void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
+    if(model->type == XRemoteRemoteItemTypeInfrared) {
+        xremote_transmit_draw_ir(canvas, model);
+    } else if(model->type == XRemoteRemoteItemTypePause) {
+        xremote_transmit_draw_pause(canvas, model);
+    }
+}
+
+XRemoteTransmit* xremote_transmit_alloc() {
+    XRemoteTransmit* instance = malloc(sizeof(XRemoteTransmit));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(XRemoteTransmitModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)xremote_transmit_draw);
+    view_set_enter_callback(instance->view, xremote_transmit_enter);
+
+    with_view_model(
+        instance->view,
+        XRemoteTransmitModel * model,
+        {
+            xremote_transmit_model_init(model);
+        },
+        true
+    );
+
+    return instance;
+}
+
+void xremote_transmit_enter(void* context) {
+    furi_assert(context);
+    XRemoteTransmit* instance = (XRemoteTransmit*) context;
+    with_view_model(
+        instance->view,
+        XRemoteTransmitModel * model,
+        {
+            xremote_transmit_model_init(model);
+        },
+        true
+    );
+}
+
+void xremote_transmit_free(XRemoteTransmit* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        XRemoteTransmitModel * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* xremote_transmit_get_view(XRemoteTransmit* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 24 - 0
views/xremote_transmit.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include "../xremote.h"
+#include "../helpers/xremote_custom_event.h"
+
+typedef struct XRemoteTransmit XRemoteTransmit;
+
+typedef void (*XRemoteTransmitCallback)(XRemoteCustomEvent event, void* context);
+
+void xremote_transmit_model_set_name(XRemoteTransmit* instance, const char* name);
+void xremote_transmit_model_set_type(XRemoteTransmit* instance, int type);
+
+void xremote_transmit_set_callback(
+    XRemoteTransmit* instance,
+    XRemoteTransmitCallback callback,
+    void* context);
+
+XRemoteTransmit* xremote_transmit_alloc();
+
+void xremote_transmit_free(XRemoteTransmit* instance);
+
+View* xremote_transmit_get_view(XRemoteTransmit* instance);
+
+void xremote_transmit_enter(void* context);

+ 170 - 0
xremote.c

@@ -0,0 +1,170 @@
+#include "xremote.h"
+
+bool xremote_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    XRemote* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+void xremote_tick_event_callback(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+//leave app if back button pressed
+bool xremote_navigation_event_callback(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+XRemote* xremote_app_alloc() {
+    XRemote* app = malloc(sizeof(XRemote));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
+    
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
+
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    app->scene_manager = scene_manager_alloc(&xremote_scene_handlers, app);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, xremote_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(app->view_dispatcher, xremote_tick_event_callback, 100);
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, xremote_custom_event_callback);
+    app->submenu = submenu_alloc();
+    app->editmenu = submenu_alloc();
+
+    // Set defaults, in case no config loaded
+    app->haptic = 1;
+    app->speaker = 1;
+    app->led = 1;
+    app->save_settings = 1;
+    app->transmitting = 0;
+
+    // Load configs
+    xremote_read_settings(app);
+
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
+
+    app->ir_remote_buffer = xremote_ir_remote_alloc();
+    app->ir_worker = infrared_worker_alloc();
+    app->cross_remote = cross_remote_alloc();
+
+    app->sg_remote_buffer = xremote_sg_remote_alloc();
+    
+    app->loading = loading_alloc();
+
+    app->text_input = text_input_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdTextInput, text_input_get_view(app->text_input));
+
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdMenu, submenu_get_view(app->submenu));
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdEditItem, submenu_get_view(app->editmenu));
+    app->xremote_infoscreen = xremote_infoscreen_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdInfoscreen, xremote_infoscreen_get_view(app->xremote_infoscreen));
+    app->xremote_transmit = xremote_transmit_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdTransmit, xremote_transmit_get_view(app->xremote_transmit));
+    app->xremote_pause_set = xremote_pause_set_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdPauseSet, xremote_pause_set_get_view(app->xremote_pause_set));
+    app->button_menu_create = button_menu_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdCreate, button_menu_get_view(app->button_menu_create));
+    app->button_menu_create_add = button_menu_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdCreateAdd, button_menu_get_view(app->button_menu_create_add));
+    app->button_menu_ir = button_menu_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdIrRemote, button_menu_get_view(app->button_menu_ir));
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdSettings, variable_item_list_get_view(app->variable_item_list));
+    
+    app->popup = popup_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, XRemoteViewIdWip, popup_get_view(app->popup));
+    app->view_stack = view_stack_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, XRemoteViewIdStack, view_stack_get_view(app->view_stack));
+
+    //End Scene Additions
+
+    return app;
+}
+
+void xremote_app_free(XRemote* app) {
+    furi_assert(app);
+    
+    // Scene manager
+    scene_manager_free(app->scene_manager);
+
+    infrared_worker_free(app->ir_worker);
+
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdCreate);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdCreateAdd);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdSettings);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdWip);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdStack);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdTextInput);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdTransmit);
+    view_dispatcher_remove_view(app->view_dispatcher, XRemoteViewIdPauseSet);
+    text_input_free(app->text_input);
+    button_menu_free(app->button_menu_create);
+    button_menu_free(app->button_menu_create_add);
+    button_menu_free(app->button_menu_ir);
+    view_stack_free(app->view_stack);
+    popup_free(app->popup);
+    submenu_free(app->submenu);
+
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(app->file_path);
+
+    loading_free(app->loading);
+    
+    app->gui = NULL;
+    app->notification = NULL;
+
+    //Remove whatever is left
+    free(app);
+}
+
+void xremote_popup_closed_callback(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, XRemoteCustomEventTypePopupClosed);
+}
+
+void xremote_text_input_callback(void* context) {
+    furi_assert(context);
+    XRemote* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, XRemoteCustomEventTextInput);
+}
+
+int32_t xremote_app(void* p) {
+    UNUSED(p);
+    XRemote* app = xremote_app_alloc();
+    
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+    
+    //scene_manager_next_scene(app->scene_manager, XRemoteSceneInfoscreen); //Start with start screen
+    scene_manager_next_scene(app->scene_manager, XRemoteSceneMenu); //if you want to directly start with Menu
+
+    furi_hal_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    xremote_save_settings(app);
+    
+    furi_hal_power_suppress_charge_exit();
+    xremote_app_free(app);
+
+    return 0;
+}
+
+
+

+ 81 - 0
xremote.h

@@ -0,0 +1,81 @@
+#pragma once
+
+#include "scenes/xremote_scene.h"
+#include "views/xremote_infoscreen.h"
+#include "views/xremote_transmit.h"
+#include "views/xremote_pause_set.h"
+#include "helpers/xremote_storage.h"
+#include "models/infrared/xremote_ir_remote.h"
+#include "models/cross/xremote_remote.h"
+#include "xremote_i.h"
+
+typedef struct {
+    Gui* gui;
+    DialogsApp* dialogs;
+    FuriString* file_path;
+    NotificationApp* notification;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    Submenu* editmenu;
+    ButtonMenu* button_menu_create;
+    ButtonMenu* button_menu_create_add;
+    ButtonMenu* button_menu_ir;
+    TextInput* text_input;
+    Popup* popup;
+    Loading* loading;
+    ViewStack* view_stack;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    XRemoteInfoscreen* xremote_infoscreen;
+    XRemoteTransmit* xremote_transmit;
+    XRemotePauseSet* xremote_pause_set;
+    InfraredRemote* ir_remote_buffer;
+    InfraredWorker* ir_worker;
+    SubGhzRemote* sg_remote_buffer;
+    CrossRemote* cross_remote;
+    uint32_t haptic; 
+    uint32_t speaker;
+    uint32_t led;
+    uint32_t save_settings;
+    uint32_t edit_item;
+    bool transmitting;
+    char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
+} XRemote;
+
+typedef enum {
+    XRemoteViewIdInfoscreen,
+    XRemoteViewIdMenu,
+    XRemoteViewIdEditItem,
+    XRemoteViewIdCreate,
+    XRemoteViewIdCreateAdd,
+    XRemoteViewIdSettings,
+    XRemoteViewIdWip,
+    XRemoteViewIdIrRemote,
+    XRemoteViewIdStack,
+    XRemoteViewIdTextInput,
+    XRemoteViewIdTransmit,
+    XRemoteViewIdPauseSet,
+} XRemoteViewId;
+
+typedef enum {
+    XRemoteHapticOff,
+    XRemoteHapticOn,
+} XRemoteHapticState;
+
+typedef enum {
+    XRemoteSpeakerOff,
+    XRemoteSpeakerOn,
+} XRemoteSpeakerState;
+
+typedef enum {
+    XRemoteLedOff,
+    XRemoteLedOn,
+} XRemoteLedState;
+
+typedef enum {
+    XRemoteSettingsOff,
+    XRemoteSettingsOn,
+} XRemoteSettingsStoreState;
+
+void xremote_popup_closed_callback(void* context);
+void xremote_text_input_callback(void* context);

+ 118 - 0
xremote_i.h

@@ -0,0 +1,118 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <furi.h>
+#include <furi_hal.h>
+
+#include <gui/gui.h>
+#include <gui/elements.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 <gui/modules/variable_item_list.h>
+
+//#include <lib/subghz/protocols/protocol_items.h> //Not found
+
+#include <input/input.h>
+#include <notification/notification_messages.h>
+
+#include <string.h>
+#include <m-array.h>
+#include <toolbox/path.h>
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+#include <core/check.h>
+#include <core/common_defines.h>
+#include <dolphin/dolphin.h>
+#include <infrared.h>
+#include <infrared_worker.h>
+#include <infrared_transmit.h>
+#include <flipper_format/flipper_format.h>
+#include "xremote_icons.h"
+#include "models/subghz/xremote_sg_remote.h"
+
+#define XREMOTE_APP_FOLDER EXT_PATH("apps_data/xremote")
+#define XREMOTE_DEFAULT_REMOTE_NAME "remote"
+#define XREMOTE_APP_EXTENSION ".xr"
+#define XREMOTE_FILE_TYPE "Cross Remote File"
+#define XREMOTE_FILE_VERSION 1
+#define XREMOTE_TEXT_STORE_NUM 2
+#define XREMOTE_TEXT_STORE_SIZE 128
+#define XREMOTE_MAX_ITEM_NAME_LENGTH 22
+#define XREMOTE_MAX_REMOTE_NAME_LENGTH 22
+
+#define INFRARED_APP_EXTENSION ".ir"
+#define INFRARED_APP_FOLDER ANY_PATH("infrared")
+
+#define SUBGHZ_APP_EXTENSION ".sub"
+#define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
+
+#define TAG "XRemote"
+
+typedef enum {
+    XRemoteRemoteItemTypeInfrared,
+    XRemoteRemoteItemTypeSubGhz,
+    XRemoteRemoteItemTypePause,
+} XRemoteRemoteItemType;
+
+typedef enum {
+    InfraredNotificationMessageSuccess,
+    InfraredNotificationMessageGreenOn,
+    InfraredNotificationMessageGreenOff,
+    InfraredNotificationMessageYellowOn,
+    InfraredNotificationMessageYellowOff,
+    InfraredNotificationMessageBlinkStartRead,
+    InfraredNotificationMessageBlinkStartSend,
+    InfraredNotificationMessageBlinkStop,
+    PauseNotificationMessageBlinkStartSend,
+    PauseNotificationMessageBlinkStop,
+    SubGhzNotificationMessageBlinkStartSend,
+    SubGhzNotificationMessageBlinkStop,
+} XRemoteNotificationMessage;
+
+typedef enum {
+    XRemoteTransmittingIdle,
+    XRemoteTransmittingStart,
+    XRemoteTransmittingStop,
+} XRemoteRemoteTransmissionStatus;
+
+struct InfraredSignal {
+    bool is_raw;
+    union {
+        InfraredMessage message;
+        InfraredRawSignal raw;
+    } payload;
+};
+
+struct CrossRemoteItem {
+    FuriString* name;
+    InfraredSignal* ir_signal;
+    SubGhzRemote* sg_signal;
+    int16_t type;
+    int32_t time;
+};
+
+typedef struct CrossRemote CrossRemote;
+typedef struct CrossRemoteItem CrossRemoteItem;
+
+typedef struct XRemoteTransmit XRemoteTransmit;
+typedef struct XRemotePauseSet XRemotePauseSet;
+//typedef struct XRemoteInfoscreen XRemoteInfoscreen;
+
+//typedef struct InfraredRemote InfraredRemote;
+//typedef struct InfraredRemoteButton InfraredRemoteButton;
+//typedef struct InfraredSignal InfraredSignal;
+//typedef struct InfraredRawSignal InfraredRawSignal;