Browse Source

Merge xremote from https://github.com/kala13x/flipper-xremote

# Conflicts:
#	xremote/xremote.h
Willy-JL 1 year ago
parent
commit
d8dcb90bda

BIN
xremote/.flipcorg/gallery/screen7.png


+ 51 - 1
xremote/README.md

@@ -1,4 +1,9 @@
 <p align="center">Advanced IR Remote App for Flipper Device</p>
 <p align="center">Advanced IR Remote App for Flipper Device</p>
+
+<p align="center">
+  Version 1.3 - <a href="https://github.com/kala13x/flipper-xremote/blob/main/docs/changelog.md">Changelog</a>
+</p>
+
 <p align="center">
 <p align="center">
     <img src="https://github.com/kala13x/flipper-xremote/blob/main/.flipcorg/banner.png" alt="XRemote">
     <img src="https://github.com/kala13x/flipper-xremote/blob/main/.flipcorg/banner.png" alt="XRemote">
 </p>
 </p>
@@ -31,6 +36,7 @@ The application is compatible with standard `.ir` files. However, to ensure func
 Button name | Description
 Button name | Description
 ------------|-------------------
 ------------|-------------------
 `Power`     | Power
 `Power`     | Power
+`Eject`     | Eject
 `Setup`     | Setup/Settings
 `Setup`     | Setup/Settings
 `Input`     | Input/Source
 `Input`     | Input/Source
 `Menu`      | Menu
 `Menu`      | Menu
@@ -57,6 +63,46 @@ Button name | Description
 `Play`      | Play
 `Play`      | Play
 `Stop`      | Stop
 `Stop`      | Stop
 
 
+## Alternative button names
+In addition to the predefined names, `XRemote` uses alternative button names to make it as easy as possible to interact with different types of IR dumps. That means if a button with the appropriate name is not found in the file, the application will try to find the same button with alternative names. Ensure this feature is enabled in the application settings before you use it.
+
+The application stores and reads alternate names from the following file:
+```
+SD Card/apps_data/flipper_xremote/alt_names.txt
+```
+
+If the `Alt-Names` option is enabled in the config and the file does not exist, it will be created automatically with default values during the application's startup. You can edit, add, or remove any button or alternate name values from this file. Button names must either have only the first uppercase or be entirely lowercase. As for alternate names, they are case-insensitive. The button can have one or several comma-separated alternate names.
+
+This is the default `alt_names.txt` file:
+
+```
+Filetype: XRemote Alt-Names
+Version: 1
+# 
+Power: shutdown,off,on,standby
+Setup: settings,config,cfg
+Input: source,select
+Menu: osd,gui
+List: guide
+Info: display
+Mode: aspect,format
+Back: return,exit
+Ok: enter,select
+Up: uparrow
+Down: downarrow
+Left: leftarrow
+Right: rightarrow
+Mute: silence,silent,unmute
+Vol_up: vol+,volume+,volup,+
+Vol_dn: vol-,volume-,voldown,-
+Ch_next: ch+,channel+,chup
+Ch_prev: ch-,channel-,chdown
+Next: next,skip,ffwd
+Prev: prev,back,rewind,rew
+Fast_fo: fastfwd,fastforward,ff
+Fast_ba: fastback,fastrewind,fb
+Play_pa: playpause,play,pause
+```
 
 
 ## Installation options
 ## Installation options
 
 
@@ -73,8 +119,10 @@ Button name | Description
    - Use deploy script from this repository to build and run the application on the device:
    - Use deploy script from this repository to build and run the application on the device:
 
 
     ```bash
     ```bash
-    ./deploy.sh --fw=/path/to/the/firmware
+    ./deploy.sh --fw=/path/to/the/firmware -b -l
     ```
     ```
+
+    Do not use `-l` (link) option of you are building the project directly from the `applications_user` directory of the firmware.
 2. If you don't have the firmware or the Linux please refer to the [official documentation](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppsOnSDCard.md) for build instructions.
 2. If you don't have the firmware or the Linux please refer to the [official documentation](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppsOnSDCard.md) for build instructions.
 
 
 ## Progress
 ## Progress
@@ -89,6 +137,7 @@ Button name | Description
   - [x] Player buttons page
   - [x] Player buttons page
   - [x] Custom buttons page
   - [x] Custom buttons page
   - [x] Edit custom layout
   - [x] Edit custom layout
+  - [x] Alternative button names
   - [ ] Add or remove button
   - [ ] Add or remove button
   - [ ] All buttons page
   - [ ] All buttons page
 - [x] Application settings
 - [x] Application settings
@@ -98,6 +147,7 @@ Button name | Description
   - [x] Vertical/horizontal views
   - [x] Vertical/horizontal views
   - [x] IR command repeat count
   - [x] IR command repeat count
   - [x] Exit button behavior
   - [x] Exit button behavior
+  - [x] Enable/disable alt names
 
 
 ## Screens
 ## Screens
 
 

+ 1 - 1
xremote/application.fam

@@ -6,7 +6,7 @@ App(
     requires=["gui", "dialogs", "infrared"],
     requires=["gui", "dialogs", "infrared"],
     stack_size=3 * 1024,
     stack_size=3 * 1024,
     order=1,
     order=1,
-    fap_version="1.2",
+    fap_version="1.3",
     fap_category="Infrared",
     fap_category="Infrared",
     fap_icon_assets="assets",
     fap_icon_assets="assets",
     fap_icon_assets_symbol="xc",
     fap_icon_assets_symbol="xc",

+ 46 - 14
xremote/deploy.sh

@@ -9,11 +9,39 @@ XCLR_DIM="\x1B[2m"
 XCLR_RED="\x1B[31m"
 XCLR_RED="\x1B[31m"
 XCLR_RESET="\x1B[0m\n"
 XCLR_RESET="\x1B[0m\n"
 
 
-# Parse firmware path from arguments if present
+FBT_CMD="./fbt"
+FBT_DBG="DEBUG=0"
+FBT_ARGS="COMPACT=1 launch"
+
+BUILD_PROJECT=0
+LINK_PROJECT=0
+RUN_QFLIPPER=0
+BUILD_DONE=0
+
 for arg in "$@"; do
 for arg in "$@"; do
     if [[ $arg == --firmware=* || $arg == --fw=* ]]; then
     if [[ $arg == --firmware=* || $arg == --fw=* ]]; then
         FLIPPER_FIRMWARE="${arg#*=}"
         FLIPPER_FIRMWARE="${arg#*=}"
     fi
     fi
+
+    if [[ $arg == "--build" || $arg == "-b" ]]; then
+        BUILD_PROJECT=1
+    fi
+
+    if [[ $arg == "--run" || $arg == "-r" ]]; then
+        RUN_PROJECT=1
+    fi
+
+    if [[ $arg == "--link" || $arg == "-l" ]]; then
+        LINK_PROJECT=1
+    fi
+
+    if [[ $arg == "--debug" || $arg == "-d" ]]; then
+        FBT_DBG="DEBUG=1"
+    fi
+
+    if [[ $arg == "--sudo" || $arg == "-s" ]]; then
+        FBT_CMD="sudo ./fbt"
+    fi
 done
 done
 
 
 # Check if FLIPPER_FIRMWARE variable is set
 # Check if FLIPPER_FIRMWARE variable is set
@@ -41,21 +69,25 @@ XREMOTE_PROJ_NAME=$(basename "$XREMOTE_PROJ_PATH")
 FLIPPER_APPSRC="applications_user/$XREMOTE_PROJ_NAME"
 FLIPPER_APPSRC="applications_user/$XREMOTE_PROJ_NAME"
 FLIPPER_USER_APP="$FLIPPER_FIRMWARE/$FLIPPER_APPSRC"
 FLIPPER_USER_APP="$FLIPPER_FIRMWARE/$FLIPPER_APPSRC"
 
 
-# Unlink existing user application first
-[ -s $FLIPPER_USER_APP ] && rm -f $FLIPPER_USER_APP
-ln -s $XREMOTE_PROJ_PATH $FLIPPER_FIRMWARE/applications_user
+link_project() {
+    [ -s $FLIPPER_USER_APP ] && rm -f $FLIPPER_USER_APP
+    ln -s $XREMOTE_PROJ_PATH $FLIPPER_FIRMWARE/applications_user
+}
 
 
-# Build and deploy the project
-cd $FLIPPER_FIRMWARE
-DEPLOY_DONE=0
-sudo ./fbt COMPACT=1 DEBUG=0 launch APPSRC=$FLIPPER_APPSRC && DEPLOY_DONE=1
+build_project() {
+    cd $FLIPPER_FIRMWARE
+    $FBT_CMD $FBT_ARGS $FBT_DBG APPSRC=$FLIPPER_APPSRC && BUILD_DONE=1
+}
 
 
-# Run qflipper command if asked
-for arg in "$@"; do
-    if [[ $arg == "--run" || $arg == "-r" ]]; then
-        [ $DEPLOY_DONE -eq 1 ] && sudo qflipper
+run_project() {
+    if [[ $BUILD_PROJECT -eq 0 || $BUILD_DONE -eq 1  ]]; then
+        qFlipper
     fi
     fi
-done
+}
+
+[ $LINK_PROJECT -eq 1 ] && link_project
+[ $BUILD_PROJECT -eq 1 ] && build_project
+[ $RUN_PROJECT -eq 1 ] && run_project
 
 
 # Return with success
 # Return with success
-exit 0
+exit 0

+ 37 - 0
xremote/docs/README.md

@@ -48,3 +48,40 @@ Play_pa     | Play/Pause
 Pause       | Pause
 Pause       | Pause
 Play        | Play
 Play        | Play
 Stop        | Stop
 Stop        | Stop
+
+## Alternative button names
+
+In addition to the predefined names, XRemote uses alternative button names to make it as easy as possible to interact with different types of IR dumps. That means if a button with the appropriate name is not found in the file, the application will try to find the same button with alternative names. Ensure this feature is enabled in the application settings before you use it.
+
+The application stores and reads alternate names from the following file:
+SD_Card/apps_data/flipper_xremote/alt_names.txt
+
+If the Alt-Names option is enabled in the config and the file does not exist, it will be created automatically with default values during the application's startup. You can edit, add, or remove any button or alternate name values from this file. Button names must either have only the first uppercase or be entirely lowercase. As for alternate names, they are case-insensitive. The button can have one or several comma-separated alternate names.
+
+## Default alternative names
+
+Button name | Alternative names
+------------|-------------------
+Power       | shutdown,off,on,standby
+Setup       | settings,config,cfg
+Input       | source,select
+Menu        | osd,gui
+List        | guide
+Info        | display
+Mode        | aspect,format
+Back        | return,exit
+Ok          | enter,select
+Up          | uparrow
+Down        | downarrow
+Left        | leftarrow
+Right       | rightarrow
+Mute        | silence,silent,unmute
+Vol_up      | vol+,volume+,volup,+
+Vol_dn      | vol-,volume-,voldown,-
+Ch_next     | ch+,channel+,chup
+Ch_prev     | ch-,channel-,chdown
+Next        | next,skip,ffwd
+Prev        | prev,back,rewind,rew
+Fast_fo     | fastfwd,fastforward,ff
+Fast_ba     | fastback,fastrewind,fb
+Play_pa     | playpause,play,pause

+ 17 - 0
xremote/docs/changelog.md

@@ -1,3 +1,20 @@
+## v1.3
+
+Alternative names
+
+- Implemented alternative names functionality
+- Implemented case-insensitive button search
+- Added Eject button support
+- Fixed bugs and refactored code
+
+## v1.2
+
+Bug fixing and performance improvements
+
+- Fixed build issues with new Flipper Zero SDK
+- Fixed several crashes and refactored code
+- Improved button search performance
+
 ## v1.1
 ## v1.1
 
 
 Custom layout and bug fixing
 Custom layout and bug fixing

+ 7 - 4
xremote/infrared/infrared_remote.c

@@ -16,6 +16,8 @@
 #include <stddef.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <stdlib.h>
 #include <m-array.h>
 #include <m-array.h>
+#include <string.h>
+#include <ctype.h>
 #include <toolbox/path.h>
 #include <toolbox/path.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 #include <core/common_defines.h>
 #include <core/common_defines.h>
@@ -89,7 +91,9 @@ InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t
 bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) {
 bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) {
     for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
     for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
         InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
         InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
-        if(!strcmp(infrared_remote_button_get_name(button), name)) {
+        FuriString* furi_name = infrared_remote_button_get_furi_name(button);
+
+        if(button && !furi_string_cmpi_str(furi_name, name)) {
             *index = i;
             *index = i;
             return true;
             return true;
         }
         }
@@ -101,9 +105,8 @@ InfraredRemoteButton*
     infrared_remote_get_button_by_name(InfraredRemote* remote, const char* name) {
     infrared_remote_get_button_by_name(InfraredRemote* remote, const char* name) {
     for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
     for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
         InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
         InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
-        if(!strcmp(infrared_remote_button_get_name(button), name)) {
-            return button;
-        }
+        FuriString* furi_name = infrared_remote_button_get_furi_name(button);
+        if(button && !furi_string_cmpi_str(furi_name, name)) return button;
     }
     }
     return NULL;
     return NULL;
 }
 }

+ 7 - 1
xremote/infrared/infrared_remote_button.c

@@ -3,7 +3,9 @@
    https://github.com/DarkFlippers/unleashed-firmware
    https://github.com/DarkFlippers/unleashed-firmware
 
 
    The original project is licensed under the GNU GPLv3
    The original project is licensed under the GNU GPLv3
-   No modifications were made to this file.
+
+   Modifications made:
+   - Added function infrared_remote_button_get_furi_name()
 */
 */
 
 
 #include "infrared_remote_button.h"
 #include "infrared_remote_button.h"
@@ -36,6 +38,10 @@ const char* infrared_remote_button_get_name(InfraredRemoteButton* button) {
     return furi_string_get_cstr(button->name);
     return furi_string_get_cstr(button->name);
 }
 }
 
 
+FuriString* infrared_remote_button_get_furi_name(InfraredRemoteButton* button) {
+    return button->name;
+}
+
 void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) {
 void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal) {
     infrared_signal_set_signal(button->signal, signal);
     infrared_signal_set_signal(button->signal, signal);
 }
 }

+ 4 - 1
xremote/infrared/infrared_remote_button.h

@@ -3,7 +3,9 @@
    https://github.com/DarkFlippers/unleashed-firmware
    https://github.com/DarkFlippers/unleashed-firmware
 
 
    The original project is licensed under the GNU GPLv3
    The original project is licensed under the GNU GPLv3
-   No modifications were made to this file.
+
+   Modifications made:
+   - Added function infrared_remote_button_get_furi_name()
 */
 */
 
 
 #pragma once
 #pragma once
@@ -17,6 +19,7 @@ void infrared_remote_button_free(InfraredRemoteButton* button);
 
 
 void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name);
 void infrared_remote_button_set_name(InfraredRemoteButton* button, const char* name);
 const char* infrared_remote_button_get_name(InfraredRemoteButton* button);
 const char* infrared_remote_button_get_name(InfraredRemoteButton* button);
+FuriString* infrared_remote_button_get_furi_name(InfraredRemoteButton* button);
 
 
 void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal);
 void infrared_remote_button_set_signal(InfraredRemoteButton* button, InfraredSignal* signal);
 InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button);
 InfraredSignal* infrared_remote_button_get_signal(InfraredRemoteButton* button);

BIN
xremote/screens/settings_menu.png


+ 111 - 27
xremote/views/xremote_common_view.c

@@ -18,29 +18,30 @@ static const XRemoteButton g_buttons[XREMOTE_BUTTON_COUNT + 1] = {
     {0, XREMOTE_COMMAND_POWER},
     {0, XREMOTE_COMMAND_POWER},
     {1, XREMOTE_COMMAND_SETUP},
     {1, XREMOTE_COMMAND_SETUP},
     {2, XREMOTE_COMMAND_INPUT},
     {2, XREMOTE_COMMAND_INPUT},
-    {3, XREMOTE_COMMAND_MENU},
-    {4, XREMOTE_COMMAND_LIST},
-    {5, XREMOTE_COMMAND_INFO},
-    {6, XREMOTE_COMMAND_BACK},
-    {7, XREMOTE_COMMAND_OK},
-    {8, XREMOTE_COMMAND_UP},
-    {9, XREMOTE_COMMAND_DOWN},
-    {10, XREMOTE_COMMAND_LEFT},
-    {11, XREMOTE_COMMAND_RIGHT},
-    {12, XREMOTE_COMMAND_JUMP_FORWARD},
-    {13, XREMOTE_COMMAND_JUMP_BACKWARD},
-    {14, XREMOTE_COMMAND_FAST_FORWARD},
-    {15, XREMOTE_COMMAND_FAST_BACKWARD},
-    {16, XREMOTE_COMMAND_PLAY_PAUSE},
-    {17, XREMOTE_COMMAND_PAUSE},
-    {18, XREMOTE_COMMAND_PLAY},
-    {19, XREMOTE_COMMAND_STOP},
-    {20, XREMOTE_COMMAND_MUTE},
-    {21, XREMOTE_COMMAND_MODE},
-    {22, XREMOTE_COMMAND_VOL_UP},
-    {23, XREMOTE_COMMAND_VOL_DOWN},
-    {24, XREMOTE_COMMAND_NEXT_CHAN},
-    {25, XREMOTE_COMMAND_PREV_CHAN},
+    {3, XREMOTE_COMMAND_EJECT},
+    {4, XREMOTE_COMMAND_MENU},
+    {5, XREMOTE_COMMAND_LIST},
+    {6, XREMOTE_COMMAND_INFO},
+    {7, XREMOTE_COMMAND_BACK},
+    {8, XREMOTE_COMMAND_OK},
+    {9, XREMOTE_COMMAND_UP},
+    {10, XREMOTE_COMMAND_DOWN},
+    {11, XREMOTE_COMMAND_LEFT},
+    {12, XREMOTE_COMMAND_RIGHT},
+    {13, XREMOTE_COMMAND_JUMP_FORWARD},
+    {14, XREMOTE_COMMAND_JUMP_BACKWARD},
+    {15, XREMOTE_COMMAND_FAST_FORWARD},
+    {16, XREMOTE_COMMAND_FAST_BACKWARD},
+    {17, XREMOTE_COMMAND_PLAY_PAUSE},
+    {18, XREMOTE_COMMAND_PAUSE},
+    {19, XREMOTE_COMMAND_PLAY},
+    {20, XREMOTE_COMMAND_STOP},
+    {21, XREMOTE_COMMAND_MUTE},
+    {22, XREMOTE_COMMAND_MODE},
+    {23, XREMOTE_COMMAND_VOL_UP},
+    {24, XREMOTE_COMMAND_VOL_DOWN},
+    {25, XREMOTE_COMMAND_NEXT_CHAN},
+    {26, XREMOTE_COMMAND_PREV_CHAN},
     {-1, NULL}};
     {-1, NULL}};
 
 
 const char* xremote_button_get_name(int index) {
 const char* xremote_button_get_name(int index) {
@@ -90,9 +91,7 @@ XRemoteView*
 
 
 void xremote_view_clear_context(XRemoteView* rview) {
 void xremote_view_clear_context(XRemoteView* rview) {
     furi_assert(rview);
     furi_assert(rview);
-
     if(rview->context && rview->on_clear) rview->on_clear(rview->context);
     if(rview->context && rview->on_clear) rview->on_clear(rview->context);
-
     rview->context = NULL;
     rview->context = NULL;
 }
 }
 
 
@@ -134,16 +133,101 @@ View* xremote_view_get_view(XRemoteView* rview) {
     return rview->view;
     return rview->view;
 }
 }
 
 
+static InfraredRemoteButton*
+    infrared_remote_get_button_by_alt_name(InfraredRemote* remote, const char* name, bool try_low) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
+    FuriString* header = furi_string_alloc();
+
+    FURI_LOG_I(XREMOTE_APP_TAG, "loading alt_names file: \'%s\'", XREMOTE_ALT_NAMES);
+
+    InfraredRemoteButton* button = NULL;
+    char key[XREMOTE_NAME_MAX] = {0};
+    bool key_found = false;
+    uint32_t version = 0;
+
+    do {
+        /* Open file and read the header */
+        if(!flipper_format_buffered_file_open_existing(ff, XREMOTE_ALT_NAMES)) break;
+        if(!flipper_format_read_header(ff, header, &version)) break;
+        if(!furi_string_equal(header, "XRemote Alt-Names") || (version != 1)) break;
+
+        FuriString* value = furi_string_alloc();
+        key_found = flipper_format_read_string(ff, name, value);
+
+        if(!key_found) {
+            if(!try_low) break;
+            size_t i;
+
+            /* Convert name to lowercase and try again */
+            for(i = 0; name[i] != '\0' && i < sizeof(key) - 1; i++) {
+                key[i] = tolower(name[i]);
+            }
+
+            key[i] = '\0';
+            break;
+        }
+
+        size_t start = 0;
+        size_t posit = furi_string_search_str(value, ",", start);
+
+        if(posit == FURI_STRING_FAILURE) {
+            const char* alt_name_cstr = furi_string_get_cstr(value);
+            button = infrared_remote_get_button_by_name(remote, alt_name_cstr);
+        } else {
+            FuriString* alt_name = furi_string_alloc();
+
+            while(posit != FURI_STRING_FAILURE) {
+                furi_string_set_n(alt_name, value, start, posit - start);
+                const char* alt_name_cstr = furi_string_get_cstr(alt_name);
+                button = infrared_remote_get_button_by_name(remote, alt_name_cstr);
+
+                furi_string_reset(alt_name);
+                if(button != NULL) break;
+
+                start = posit + 1; // Move to the next position
+                posit = furi_string_search_str(value, ",", start);
+            }
+
+            if(posit == FURI_STRING_FAILURE && button == NULL) {
+                size_t str_len = furi_string_utf8_length(value);
+                furi_string_set_n(alt_name, value, start, str_len - start);
+                const char* alt_name_cstr = furi_string_get_cstr(alt_name);
+                button = infrared_remote_get_button_by_name(remote, alt_name_cstr);
+            }
+
+            furi_string_free(alt_name);
+        }
+
+    } while(false);
+
+    furi_record_close(RECORD_STORAGE);
+    furi_string_free(header);
+    flipper_format_free(ff);
+
+    if(!key_found && try_low) return infrared_remote_get_button_by_alt_name(remote, key, false);
+
+    return button;
+}
+
 InfraredRemoteButton* xremote_view_get_button_by_name(XRemoteView* rview, const char* name) {
 InfraredRemoteButton* xremote_view_get_button_by_name(XRemoteView* rview, const char* name) {
     xremote_app_assert(rview->context, NULL);
     xremote_app_assert(rview->context, NULL);
+    xremote_app_assert(rview->app_ctx, NULL);
+
+    XRemoteAppSettings* settings = rview->app_ctx->app_settings;
     XRemoteAppButtons* buttons = (XRemoteAppButtons*)rview->context;
     XRemoteAppButtons* buttons = (XRemoteAppButtons*)rview->context;
-    return infrared_remote_get_button_by_name(buttons->remote, name);
+    InfraredRemoteButton* button = infrared_remote_get_button_by_name(buttons->remote, name);
+
+    if(button == NULL && settings->alt_names)
+        button = infrared_remote_get_button_by_alt_name(buttons->remote, name, true);
+
+    return button;
 }
 }
 
 
 bool xremote_view_press_button(XRemoteView* rview, InfraredRemoteButton* button) {
 bool xremote_view_press_button(XRemoteView* rview, InfraredRemoteButton* button) {
     xremote_app_assert(button, false);
     xremote_app_assert(button, false);
-    XRemoteAppSettings* settings = rview->app_ctx->app_settings;
 
 
+    XRemoteAppSettings* settings = rview->app_ctx->app_settings;
     InfraredSignal* signal = infrared_remote_button_get_signal(button);
     InfraredSignal* signal = infrared_remote_button_get_signal(button);
     xremote_app_assert(signal, false);
     xremote_app_assert(signal, false);
 
 

+ 3 - 2
xremote/views/xremote_common_view.h

@@ -22,10 +22,11 @@
 
 
 #include "../infrared/infrared_remote.h"
 #include "../infrared/infrared_remote.h"
 
 
-#define XREMOTE_BUTTON_COUNT 26
-#define XREMOTE_NAME_MAX 16
+#define XREMOTE_BUTTON_COUNT 27
+#define XREMOTE_NAME_MAX 32
 
 
 #define XREMOTE_COMMAND_POWER "Power"
 #define XREMOTE_COMMAND_POWER "Power"
+#define XREMOTE_COMMAND_EJECT "Eject"
 #define XREMOTE_COMMAND_SETUP "Setup"
 #define XREMOTE_COMMAND_SETUP "Setup"
 #define XREMOTE_COMMAND_INPUT "Input"
 #define XREMOTE_COMMAND_INPUT "Input"
 #define XREMOTE_COMMAND_MENU "Menu"
 #define XREMOTE_COMMAND_MENU "Menu"

+ 1 - 1
xremote/xremote.h

@@ -9,7 +9,7 @@
 #include "xremote_app.h"
 #include "xremote_app.h"
 
 
 #define XREMOTE_VERSION_MAJ 1
 #define XREMOTE_VERSION_MAJ 1
-#define XREMOTE_VERSION_MIN 2
+#define XREMOTE_VERSION_MIN 3
 #define XREMOTE_BUILD_NUMBER 0
 #define XREMOTE_BUILD_NUMBER 0
 
 
 /* Returns FAP_VERSION + XREMOTE_BUILD_NUMBER */
 /* Returns FAP_VERSION + XREMOTE_BUILD_NUMBER */

+ 70 - 10
xremote/xremote_app.c

@@ -13,7 +13,6 @@
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 
 
 #define XREMOTE_APP_SETTINGS APP_DATA_PATH("xremote.cfg")
 #define XREMOTE_APP_SETTINGS APP_DATA_PATH("xremote.cfg")
-#define TAG "XRemoteApp"
 
 
 #define XREMOTE_ORIENTATION_TEXT_HORIZONTAL "Horizontal"
 #define XREMOTE_ORIENTATION_TEXT_HORIZONTAL "Horizontal"
 #define XREMOTE_ORIENTATION_TEXT_VERTICAL "Vertical"
 #define XREMOTE_ORIENTATION_TEXT_VERTICAL "Vertical"
@@ -44,6 +43,10 @@ const char* xremote_app_get_exit_str(XRemoteAppExit exit_behavior) {
     return exit_behavior == XRemoteAppExitPress ? "Press" : "Hold";
     return exit_behavior == XRemoteAppExitPress ? "Press" : "Hold";
 }
 }
 
 
+const char* xremote_app_get_alt_names_str(uint8_t alt_names_index) {
+    return alt_names_index ? "On" : "Off";
+}
+
 const char* xremote_app_get_orientation_str(ViewOrientation view_orientation) {
 const char* xremote_app_get_orientation_str(ViewOrientation view_orientation) {
     return view_orientation == ViewOrientationHorizontal ? "Horizontal" : "Vertical";
     return view_orientation == ViewOrientationHorizontal ? "Horizontal" : "Vertical";
 }
 }
@@ -145,6 +148,49 @@ bool xremote_app_extension_store(XRemoteAppButtons* buttons, FuriString* path) {
     return success;
     return success;
 }
 }
 
 
+bool xremote_app_alt_names_check_and_init() {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = false;
+
+    do {
+        if(!flipper_format_file_open_new(ff, XREMOTE_ALT_NAMES)) break;
+        if(!flipper_format_write_header_cstr(ff, "XRemote Alt-Names", 1)) break;
+        if(!flipper_format_write_comment_cstr(ff, "")) break;
+
+        if(!flipper_format_write_string_cstr(ff, "Power", "shutdown,off,on,standby")) break;
+        if(!flipper_format_write_string_cstr(ff, "Setup", "settings,config,cfg")) break;
+        if(!flipper_format_write_string_cstr(ff, "Input", "source,select")) break;
+        if(!flipper_format_write_string_cstr(ff, "Menu", "osd,gui")) break;
+        if(!flipper_format_write_string_cstr(ff, "List", "guide")) break;
+        if(!flipper_format_write_string_cstr(ff, "Info", "display")) break;
+        if(!flipper_format_write_string_cstr(ff, "Mode", "aspect,format")) break;
+        if(!flipper_format_write_string_cstr(ff, "Back", "return,exit")) break;
+        if(!flipper_format_write_string_cstr(ff, "Ok", "enter,select")) break;
+        if(!flipper_format_write_string_cstr(ff, "Up", "uparrow")) break;
+        if(!flipper_format_write_string_cstr(ff, "Down", "downarrow")) break;
+        if(!flipper_format_write_string_cstr(ff, "Left", "leftarrow")) break;
+        if(!flipper_format_write_string_cstr(ff, "Right", "rightarrow")) break;
+        if(!flipper_format_write_string_cstr(ff, "Mute", "silence,silent,unmute")) break;
+        if(!flipper_format_write_string_cstr(ff, "Vol_up", "vol+,volume+,volup,+")) break;
+        if(!flipper_format_write_string_cstr(ff, "Vol_dn", "vol-,volume-,voldown,-")) break;
+        if(!flipper_format_write_string_cstr(ff, "Ch_next", "ch+,channel+,chup")) break;
+        if(!flipper_format_write_string_cstr(ff, "Ch_prev", "ch-,channel-,chdown")) break;
+        if(!flipper_format_write_string_cstr(ff, "Next", "next,skip,ffwd")) break;
+        if(!flipper_format_write_string_cstr(ff, "Prev", "prev,back,rewind,rew")) break;
+        if(!flipper_format_write_string_cstr(ff, "Fast_fo", "fastfwd,fastforward,ff")) break;
+        if(!flipper_format_write_string_cstr(ff, "Fast_ba", "fastback,fastrewind,fb")) break;
+        if(!flipper_format_write_string_cstr(ff, "Play_pa", "playpause,play,pause")) break;
+
+        success = true;
+    } while(false);
+
+    furi_record_close(RECORD_STORAGE);
+    flipper_format_free(ff);
+
+    return success;
+}
+
 void xremote_app_buttons_free(XRemoteAppButtons* buttons) {
 void xremote_app_buttons_free(XRemoteAppButtons* buttons) {
     xremote_app_assert_void(buttons);
     xremote_app_assert_void(buttons);
     infrared_remote_free(buttons->remote);
     infrared_remote_free(buttons->remote);
@@ -183,7 +229,7 @@ XRemoteAppButtons* xremote_app_buttons_alloc() {
 
 
 XRemoteAppButtons* xremote_app_buttons_load(XRemoteAppContext* app_ctx) {
 XRemoteAppButtons* xremote_app_buttons_load(XRemoteAppContext* app_ctx) {
     /* Show file selection dialog (returns selected file path with app_ctx->file_path) */
     /* Show file selection dialog (returns selected file path with app_ctx->file_path) */
-    if(!xremote_app_browser_select_file(app_ctx, XREMOTE_APP_EXTENSION)) return NULL;
+    if(!xremote_app_context_select_file(app_ctx, XREMOTE_APP_EXTENSION)) return NULL;
     XRemoteAppButtons* buttons = xremote_app_buttons_alloc();
     XRemoteAppButtons* buttons = xremote_app_buttons_alloc();
     buttons->app_ctx = app_ctx;
     buttons->app_ctx = app_ctx;
 
 
@@ -207,6 +253,7 @@ XRemoteAppSettings* xremote_app_settings_alloc() {
     settings->orientation = ViewOrientationHorizontal;
     settings->orientation = ViewOrientationHorizontal;
     settings->exit_behavior = XRemoteAppExitPress;
     settings->exit_behavior = XRemoteAppExitPress;
     settings->repeat_count = 2;
     settings->repeat_count = 2;
+    settings->alt_names = 1;
     return settings;
     return settings;
 }
 }
 
 
@@ -219,7 +266,7 @@ bool xremote_app_settings_store(XRemoteAppSettings* settings) {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     FlipperFormat* ff = flipper_format_file_alloc(storage);
     FlipperFormat* ff = flipper_format_file_alloc(storage);
 
 
-    FURI_LOG_I(TAG, "store config file: \'%s\'", XREMOTE_APP_SETTINGS);
+    FURI_LOG_I(XREMOTE_APP_TAG, "store config file: \'%s\'", XREMOTE_APP_SETTINGS);
     bool success = false;
     bool success = false;
 
 
     do {
     do {
@@ -238,6 +285,9 @@ bool xremote_app_settings_store(XRemoteAppSettings* settings) {
         value = settings->repeat_count;
         value = settings->repeat_count;
         if(!flipper_format_write_uint32(ff, "repeat", &value, 1)) break;
         if(!flipper_format_write_uint32(ff, "repeat", &value, 1)) break;
 
 
+        value = settings->alt_names;
+        if(!flipper_format_write_uint32(ff, "altNames", &value, 1)) break;
+
         success = true;
         success = true;
     } while(false);
     } while(false);
 
 
@@ -252,7 +302,7 @@ bool xremote_app_settings_load(XRemoteAppSettings* settings) {
     FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
     FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
     FuriString* header = furi_string_alloc();
     FuriString* header = furi_string_alloc();
 
 
-    FURI_LOG_I(TAG, "load config file: \'%s\'", XREMOTE_APP_SETTINGS);
+    FURI_LOG_I(XREMOTE_APP_TAG, "load config file: \'%s\'", XREMOTE_APP_SETTINGS);
     uint32_t version = 0;
     uint32_t version = 0;
     uint32_t value = 0;
     uint32_t value = 0;
     bool success = false;
     bool success = false;
@@ -273,6 +323,9 @@ bool xremote_app_settings_load(XRemoteAppSettings* settings) {
         if(!flipper_format_read_uint32(ff, "repeat", &value, 1)) break;
         if(!flipper_format_read_uint32(ff, "repeat", &value, 1)) break;
         settings->repeat_count = value;
         settings->repeat_count = value;
 
 
+        if(!flipper_format_read_uint32(ff, "altNames", &value, 1)) break;
+        settings->alt_names = value;
+
         success = true;
         success = true;
     } while(false);
     } while(false);
 
 
@@ -300,6 +353,9 @@ XRemoteAppContext* xremote_app_context_alloc(void* arg) {
     ctx->app_settings = xremote_app_settings_alloc();
     ctx->app_settings = xremote_app_settings_alloc();
     xremote_app_settings_load(ctx->app_settings);
     xremote_app_settings_load(ctx->app_settings);
 
 
+    /* Initialize alternative names */
+    if(ctx->app_settings->alt_names) xremote_app_alt_names_check_and_init();
+
     /* Allocate and setup view dispatcher */
     /* Allocate and setup view dispatcher */
     ctx->view_dispatcher = view_dispatcher_alloc();
     ctx->view_dispatcher = view_dispatcher_alloc();
     view_dispatcher_enable_queue(ctx->view_dispatcher);
     view_dispatcher_enable_queue(ctx->view_dispatcher);
@@ -326,24 +382,23 @@ void xremote_app_context_free(XRemoteAppContext* ctx) {
     free(ctx);
     free(ctx);
 }
 }
 
 
-bool xremote_app_browser_select_file(XRemoteAppContext* app_ctx, const char* extension) {
+bool xremote_app_browser_select_file(FuriString** file_path, const char* extension) {
     DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
     DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     Storage* storage = furi_record_open(RECORD_STORAGE);
     storage_simply_mkdir(storage, XREMOTE_APP_FOLDER);
     storage_simply_mkdir(storage, XREMOTE_APP_FOLDER);
 
 
-    if(app_ctx->file_path == NULL) {
-        app_ctx->file_path = furi_string_alloc();
-        furi_string_set(app_ctx->file_path, XREMOTE_APP_FOLDER);
+    if(*file_path == NULL) {
+        *file_path = furi_string_alloc();
+        furi_string_set(*file_path, XREMOTE_APP_FOLDER);
     }
     }
 
 
     /* Open file browser (view and dialogs are managed by the browser itself) */
     /* Open file browser (view and dialogs are managed by the browser itself) */
     DialogsFileBrowserOptions browser;
     DialogsFileBrowserOptions browser;
     dialog_file_browser_set_basic_options(&browser, extension, &I_IR_Icon_10x10);
     dialog_file_browser_set_basic_options(&browser, extension, &I_IR_Icon_10x10);
     browser.base_path = XREMOTE_APP_FOLDER;
     browser.base_path = XREMOTE_APP_FOLDER;
-    FuriString* path = app_ctx->file_path;
 
 
     /* Show file selection dialog (returns selected file path with file_path) */
     /* Show file selection dialog (returns selected file path with file_path) */
-    bool status = dialog_file_browser_show(dialogs, path, path, &browser);
+    bool status = dialog_file_browser_show(dialogs, *file_path, *file_path, &browser);
 
 
     /* Cleanup file loading context */
     /* Cleanup file loading context */
     furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_STORAGE);
@@ -352,6 +407,11 @@ bool xremote_app_browser_select_file(XRemoteAppContext* app_ctx, const char* ext
     return status;
     return status;
 }
 }
 
 
+bool xremote_app_context_select_file(XRemoteAppContext* app_ctx, const char* extension) {
+    if(app_ctx == NULL) return false;
+    return xremote_app_browser_select_file(&app_ctx->file_path, extension);
+}
+
 const char* xremote_app_context_get_exit_str(XRemoteAppContext* app_ctx) {
 const char* xremote_app_context_get_exit_str(XRemoteAppContext* app_ctx) {
     XRemoteAppExit exit_behavior = app_ctx->app_settings->exit_behavior;
     XRemoteAppExit exit_behavior = app_ctx->app_settings->exit_behavior;
     return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";
     return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";

+ 10 - 2
xremote/xremote_app.h

@@ -34,9 +34,13 @@
 // XRemote generic functions and definitions
 // XRemote generic functions and definitions
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 
 
+#define XREMOTE_APP_TEXT_MAX 128
 #define XREMOTE_APP_EXTENSION ".ir"
 #define XREMOTE_APP_EXTENSION ".ir"
+#define XREMOTE_APP_TAG "XRemoteApp"
+
 #define XREMOTE_APP_FOLDER ANY_PATH("infrared")
 #define XREMOTE_APP_FOLDER ANY_PATH("infrared")
-#define XREMOTE_APP_TEXT_MAX 128
+#define XREMOTE_APP_SETTINGS APP_DATA_PATH("xremote.cfg")
+#define XREMOTE_ALT_NAMES APP_DATA_PATH("alt_names.txt")
 
 
 #define xremote_app_assert_void(cond) \
 #define xremote_app_assert_void(cond) \
     if(!cond) return
     if(!cond) return
@@ -51,6 +55,7 @@ uint32_t xremote_app_get_exit_index(XRemoteAppExit exit_behavior);
 
 
 ViewOrientation xremote_app_get_orientation(uint8_t orientation_index);
 ViewOrientation xremote_app_get_orientation(uint8_t orientation_index);
 const char* xremote_app_get_orientation_str(ViewOrientation view_orientation);
 const char* xremote_app_get_orientation_str(ViewOrientation view_orientation);
+const char* xremote_app_get_alt_names_str(uint8_t alt_names_index);
 uint32_t xremote_app_get_orientation_index(ViewOrientation view_orientation);
 uint32_t xremote_app_get_orientation_index(ViewOrientation view_orientation);
 
 
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
@@ -61,6 +66,7 @@ typedef struct {
     ViewOrientation orientation;
     ViewOrientation orientation;
     XRemoteAppExit exit_behavior;
     XRemoteAppExit exit_behavior;
     uint32_t repeat_count;
     uint32_t repeat_count;
+    uint32_t alt_names;
 } XRemoteAppSettings;
 } XRemoteAppSettings;
 
 
 XRemoteAppSettings* xremote_app_settings_alloc();
 XRemoteAppSettings* xremote_app_settings_alloc();
@@ -89,7 +95,8 @@ const char* xremote_app_context_get_exit_str(XRemoteAppContext* app_ctx);
 void xremote_app_context_notify_led(XRemoteAppContext* app_ctx);
 void xremote_app_context_notify_led(XRemoteAppContext* app_ctx);
 void xremote_app_notification_blink(NotificationApp* notifications);
 void xremote_app_notification_blink(NotificationApp* notifications);
 bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal);
 bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal);
-bool xremote_app_browser_select_file(XRemoteAppContext* app_ctx, const char* extension);
+bool xremote_app_context_select_file(XRemoteAppContext* app_ctx, const char* extension);
+bool xremote_app_browser_select_file(FuriString** file_path, const char* extension);
 
 
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 // XRemote buttons and custom button pairs
 // XRemote buttons and custom button pairs
@@ -116,6 +123,7 @@ XRemoteAppButtons* xremote_app_buttons_load(XRemoteAppContext* app_ctx);
 
 
 bool xremote_app_extension_store(XRemoteAppButtons* buttons, FuriString* path);
 bool xremote_app_extension_store(XRemoteAppButtons* buttons, FuriString* path);
 bool xremote_app_extension_load(XRemoteAppButtons* buttons, FuriString* path);
 bool xremote_app_extension_load(XRemoteAppButtons* buttons, FuriString* path);
+bool xremote_app_alt_names_check_and_init();
 
 
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 // XRemote application factory
 // XRemote application factory

+ 27 - 0
xremote/xremote_settings.c

@@ -22,6 +22,9 @@ typedef struct {
 #define XREMOTE_REPEAT_TEXT "IR Msg Repeat"
 #define XREMOTE_REPEAT_TEXT "IR Msg Repeat"
 #define XREMOTE_REPEAT_MAX 128
 #define XREMOTE_REPEAT_MAX 128
 
 
+#define XREMOTE_ALT_NAMES_TEXT "Alt Names"
+#define XREMOTE_ALT_NAMES_MAX 2
+
 static uint32_t xremote_settings_view_exit_callback(void* context) {
 static uint32_t xremote_settings_view_exit_callback(void* context) {
     UNUSED(context);
     UNUSED(context);
     return XRemoteViewSubmenu;
     return XRemoteViewSubmenu;
@@ -63,6 +66,18 @@ static void infrared_settings_exit_changed(VariableItem* item) {
     xremote_app_settings_store(settings);
     xremote_app_settings_store(settings);
 }
 }
 
 
+static void infrared_settings_alt_names_changed(VariableItem* item) {
+    XRemoteSettingsContext* ctx = variable_item_get_context(item);
+    XRemoteAppSettings* settings = ctx->app_ctx->app_settings;
+
+    settings->alt_names = variable_item_get_current_value_index(item);
+    const char* alt_names_str = xremote_app_get_alt_names_str(settings->alt_names);
+
+    if(settings->alt_names) xremote_app_alt_names_check_and_init();
+    variable_item_set_current_value_text(item, alt_names_str);
+    xremote_app_settings_store(settings);
+}
+
 static XRemoteSettingsContext* xremote_settings_context_alloc(XRemoteAppContext* app_ctx) {
 static XRemoteSettingsContext* xremote_settings_context_alloc(XRemoteAppContext* app_ctx) {
     XRemoteSettingsContext* context = malloc(sizeof(XRemoteSettingsContext));
     XRemoteSettingsContext* context = malloc(sizeof(XRemoteSettingsContext));
     XRemoteAppSettings* settings = app_ctx->app_settings;
     XRemoteAppSettings* settings = app_ctx->app_settings;
@@ -121,6 +136,18 @@ static XRemoteSettingsContext* xremote_settings_context_alloc(XRemoteAppContext*
     variable_item_set_current_value_index(item, exit_index);
     variable_item_set_current_value_index(item, exit_index);
     variable_item_set_current_value_text(item, exit_str);
     variable_item_set_current_value_text(item, exit_str);
 
 
+    /* Add exit behavior to variable item list */
+    item = variable_item_list_add(
+        context->item_list,
+        XREMOTE_ALT_NAMES_TEXT,
+        XREMOTE_ALT_NAMES_MAX,
+        infrared_settings_alt_names_changed,
+        context);
+
+    /* Set exit behavior item index and string */
+    variable_item_set_current_value_index(item, settings->alt_names);
+    variable_item_set_current_value_text(item, xremote_app_get_alt_names_str(settings->alt_names));
+
     return context;
     return context;
 }
 }