Skip to content

Commit

Permalink
Implemented custom layout page and button editing menu (#4)
Browse files Browse the repository at this point in the history
* Refactored the codebase, moved common functionality outside the apps

* Implemented functionality to load/store buttons for custom layout

* Implemented button logic in custom layout

* Implemented custom layout edit page

* Finished custom layout

* Updated exit button behavior on custom page

* Updated remote

* Updated version and changelog
  • Loading branch information
kala13x committed Oct 19, 2023
1 parent c26ea5c commit 73220eb
Show file tree
Hide file tree
Showing 24 changed files with 922 additions and 188 deletions.
51 changes: 35 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
# flipper-xremote
Advanced IR Remote App for Flipper Device

## About
Advanced IR Remote App for Flipper Device

## Idea

Navigation to the menu to press each button individually can be often uncomfortable because it requires scrolling to the desired button and selecting it. The idea behind `XRemote` is that all physical buttons are pre-mapped to specific category buttons, and a physical button directly sends an infrared signal. This allows the flipper device to be used as a remote rather than as a tool that has a remote.

## Learn new remote

`XRemote` also introduces a more user-friendly learning approach. Instead of having to manually name each button on the flipper when cloning a remote, the learning tool informs you upfront which buttons it will record. All you need to do is press the corresponding button on your existing remote, eliminating the need to name them individually.

## Custom Layout

To customize your layout, open the saved remote file, select `Edit` in the menu, and configure which infrared commands should be transmitted when physical buttons are pressed or held. These changes will be stored in the existing remote file, which means that the configuration of custom buttons can be different for all remotes.

<table align="center">
<tr>
<td align="center">Edit custom page buttons</td>
</tr>
<tr>
<td><img src="https://github.com/kala13x/flipper-xremote/blob/main/screens/custom_layout.png" alt="XRemote edit layout"></td>
</tr>
</table>

## Standard file support

The application is compatible with standard `.ir` files. However, to ensure functionality, names within these files must align with the predefined naming scheme. If the button is not highlighted when pressed or the notification LED does not light up, the button with the appropriate name cannot be found in the file.

Button name | Description
Expand Down Expand Up @@ -43,21 +62,21 @@ Button name | Description
- [x] Learn new remote
- [x] Signal analyzer
- [x] Use saved remote
- [x] General button page
- [x] Control buttons page
- [x] Navigation buttons page
- [x] Player buttons page
- [ ] Custom buttons page
- [ ] Full button list
- [ ] Rename remote file
- [ ] Delete remote file
- [x] General button page
- [x] Control buttons page
- [x] Navigation buttons page
- [x] Player buttons page
- [x] Custom buttons page
- [x] Edit custom layout
- [ ] Add or remove button
- [ ] All buttons page
- [x] Application settings
- [x] GUI to change settings
- [x] Load settings from the file
- [x] Store settings to the file
- [x] Vertical/horizontal views
- [x] IR command repeat count
- [x] Exit button behavior
- [x] GUI to change settings
- [x] Load settings from the file
- [x] Store settings to the file
- [x] Vertical/horizontal views
- [x] IR command repeat count
- [x] Exit button behavior

## Screens

Expand Down
2 changes: 1 addition & 1 deletion application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ App(
requires=["gui", "dialogs", "infrared"],
stack_size=3 * 1024,
order=1,
fap_version="1.0",
fap_version="1.1",
fap_category="Infrared",
fap_icon_assets="assets",
fap_icon_assets_symbol="xc",
Expand Down
35 changes: 33 additions & 2 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,38 @@
# This source is part of "flipper-xremote" project
# 2023 - Sandro Kalatozishvili ([email protected])

# Change it according to the root path of the used firmware
FLIPPER_FIRMWARE="/opt/flipper/firmwares/unleashed-firmware"
#FLIPPER_FIRMWARE="/opt/flipper/firmwares/unleashed-firmware"
#FLIPPER_FIRMWARE="/opt/flipper/firmwares/flipperzero-firmware"

XCLR_DIM="\x1B[2m"
XCLR_RED="\x1B[31m"
XCLR_RESET="\x1B[0m\n"

# Parse firmware path from arguments if present
for arg in "$@"; do
if [[ $arg == --firmware=* || $arg == --fw=* ]]; then
FLIPPER_FIRMWARE="${arg#*=}"
fi
done

# Check if FLIPPER_FIRMWARE variable is set
if [ -z "$FLIPPER_FIRMWARE" ]; then
echo -e "$XCLR_RED""FLIPPER_FIRMWARE variable is not set or is empty. $XCLR_RESET"
echo "You can either export FLIPPER_FIRMWARE variable:"
echo -e "$XCLR_DIM""export FLIPPER_FIRMWARE=/path/to/firmware $XCLR_RESET"
echo "Or pass the firmware path as an argument:"
echo -e "$XCLR_DIM""$0 --fw=/path/to/firmware $XCLR_RESET"
exit 1
else
echo "Using firmware path: $FLIPPER_FIRMWARE"
fi

# Check if the path exists and has a applications_user sub directory
if [[ ! -d "$FLIPPER_FIRMWARE" || ! -d "$FLIPPER_FIRMWARE/applications_user" || ! -f "$FLIPPER_FIRMWARE/fbt" ]]; then
echo -e "$XCLR_RED""Firmware path does not exist or does not contain the required flipper context. $XCLR_RESET"
exit 1
fi

# Private variables
XREMOTE_PROJ_PATH=$(dirname $(readlink -f "$0"))
XREMOTE_PROJ_NAME=$(basename "$XREMOTE_PROJ_PATH")
Expand All @@ -28,3 +56,6 @@ for arg in "$@"; do
[ $DEPLOY_DONE -eq 1 ] && sudo qflipper
fi
done

# Return with success
exit 0
24 changes: 17 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
## flipper-xremote
Advanced IR Remote App for Flipper Device
# flipper-xremote

## About
Navigation to the menu to press each button individually can be often uncomfortable because it requires scrolling to the desired button and selecting it. The idea behind XRemote is that all physical buttons are pre-mapped to specific category buttons, and a physical button directly sends an infrared signal. This allows the flipper device to be used as a remote rather than as a tool that has a remote.
Advanced IR Remote App for Flipper Device

XRemote also introduces a more user-friendly learning approach. Instead of having to manually name each button on the flipper when cloning a remote, the learning tool informs you upfront which buttons it will record. All you need to do is press the corresponding button on your existing remote, eliminating the need to name them individually.
## Idea

The application is compatible with standard .ir files. However, to ensure functionality, names within these files must align with the predefined naming scheme. If the button is not highlighted when pressed or the notification LED does not light up, the button with the appropriate name cannot be found in the file.
Navigation to the menu to press each button individually can be often uncomfortable because it requires scrolling to the desired button and selecting it. The idea behind `XRemote` is that all physical buttons are pre-mapped to specific category buttons, and a physical button directly sends an infrared signal. This allows the flipper device to be used as a remote rather than as a tool that has a remote.

## Learn new remote

`XRemote` also introduces a more user-friendly learning approach. Instead of having to manually name each button on the flipper when cloning a remote, the learning tool informs you upfront which buttons it will record. All you need to do is press the corresponding button on your existing remote, eliminating the need to name them individually.

## Custom Layout

To customize your layout, open the saved remote file, select `Edit` in the menu, and configure which infrared commands should be transmitted when physical buttons are pressed or held. These changes will be stored in the existing remote file, which means that the configuration of custom buttons can be different for all remotes.

## Standard file support

The application is compatible with standard `.ir` files. However, to ensure functionality, names within these files must align with the predefined naming scheme. If the button is not highlighted when pressed or the notification LED does not light up, the button with the appropriate name cannot be found in the file.

## Button schema

Button name | Description
------------|-------------------
Power | Power
Expand Down Expand Up @@ -37,4 +48,3 @@ Play_pa | Play/Pause
Pause | Pause
Play | Play
Stop | Stop

13 changes: 12 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
## v1.1

Custom layout and bug fixing

- Implemented custom layout page and button editor
- Updated exit button behavior on general page
- Fixed crash on dev branch firmware builds
- Refactored the codebase and fixed bugs

## v1.0

First stable release

- Adjusted layout of remote apps
- Added learn mode support
- Added signal analyzer app
Expand All @@ -9,7 +19,8 @@ First stable release
## v0.9

First beta release

- Stable saved remote control apps
- Flipper standard .ir file support
- Horizontal/Vertical view support for all apps
- Settings variable item list and functionality
- Settings variable item list and functionality
Binary file added screens/custom_layout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screens/saved_remote_menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified screens/settings_menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 44 additions & 4 deletions views/xremote_common_view.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,29 @@ const char* xremote_button_get_name(int index) {
return g_buttons[index].name;
}

int xremote_button_get_index(const char* name) {
size_t i;
for(i = 0; i < XREMOTE_BUTTON_COUNT; i++) {
if(!strcmp(name, g_buttons[i].name)) return g_buttons[i].index;
}
return -1;
}

struct XRemoteView {
XRemoteClearCallback on_clear;
XRemoteAppContext* app_ctx;
View* view;
void* context;
};

XRemoteView* xremote_view_alloc_empty() {
XRemoteView* remote_view = malloc(sizeof(XRemoteView));
return remote_view;
}

XRemoteView*
xremote_view_alloc(void* app_ctx, ViewInputCallback input_cb, ViewDrawCallback draw_cb) {
XRemoteView* remote_view = malloc(sizeof(XRemoteView));
XRemoteView* remote_view = xremote_view_alloc_empty();
remote_view->app_ctx = app_ctx;
remote_view->view = view_alloc();

Expand Down Expand Up @@ -89,11 +102,21 @@ void xremote_view_set_context(XRemoteView* rview, void* context, XRemoteClearCal
rview->on_clear = on_clear;
}

void xremote_view_set_view(XRemoteView* rview, View* view) {
xremote_view_clear_context(rview);
rview->view = view;
}

void* xremote_view_get_context(XRemoteView* rview) {
furi_assert(rview);
return rview->context;
}

void xremote_view_set_app_context(XRemoteView* rview, void* app_ctx) {
furi_assert(rview);
rview->app_ctx = app_ctx;
}

void* xremote_view_get_app_context(XRemoteView* rview) {
furi_assert(rview);
return rview->app_ctx;
Expand All @@ -113,8 +136,8 @@ View* xremote_view_get_view(XRemoteView* rview) {

InfraredRemoteButton* xremote_view_get_button_by_name(XRemoteView* rview, const char* name) {
xremote_app_assert(rview->context, NULL);
InfraredRemote* remote = (InfraredRemote*)rview->context;
return infrared_remote_get_button_by_name(remote, name);
XRemoteAppButtons* buttons = (XRemoteAppButtons*)rview->context;
return infrared_remote_get_button_by_name(buttons->remote, name);
}

bool xremote_view_press_button(XRemoteView* rview, InfraredRemoteButton* button) {
Expand All @@ -135,6 +158,23 @@ bool xremote_view_send_ir_msg_by_name(XRemoteView* rview, const char* name) {
return (button != NULL) ? xremote_view_press_button(rview, button) : false;
}

void xremote_view_model_context_set(XRemoteView* rview, void* model_ctx) {
with_view_model(
xremote_view_get_view(rview),
XRemoteViewModel * model,
{
model->context = model_ctx;
model->up_pressed = false;
model->down_pressed = false;
model->left_pressed = false;
model->right_pressed = false;
model->back_pressed = false;
model->ok_pressed = false;
model->hold = false;
},
true);
}

void xremote_canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, XRemoteIcon icon) {
if(icon == XRemoteIconEnter) {
canvas_draw_circle(canvas, x - 2, y, 4);
Expand Down Expand Up @@ -261,7 +301,7 @@ void xremote_canvas_draw_button_wide(
bool pressed,
uint8_t x,
uint8_t y,
char* text,
const char* text,
XRemoteIcon icon) {
elements_slightly_rounded_frame(canvas, x + 4, y, 56, 15);

Expand Down
26 changes: 20 additions & 6 deletions views/xremote_common_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "../infrared/infrared_remote.h"

#define XREMOTE_BUTTON_COUNT 26
#define XREMOTE_NAME_MAX 16

#define XREMOTE_COMMAND_POWER "Power"
#define XREMOTE_COMMAND_SETUP "Setup"
#define XREMOTE_COMMAND_INPUT "Input"
Expand Down Expand Up @@ -82,13 +84,14 @@ typedef enum {
} XRemoteIcon;

typedef struct {
void* context;
bool ok_pressed;
bool back_pressed;
bool up_pressed;
bool down_pressed;
bool left_pressed;
bool right_pressed;
void* context;
bool hold;
} XRemoteViewModel;

typedef enum {
Expand All @@ -109,17 +112,22 @@ typedef enum {
XRemoteViewIRSubmenu,
XRemoteViewIRGeneral,
XRemoteViewIRControl,
XRemoteViewIRPlayback,
XRemoteViewIRNavigation,
XRemoteViewIRPlayer,
XRemoteViewIRCustom
XRemoteViewIRCustomPage,
XRemoteViewIRCustomEditPage,
XRemoteViewIRAllButtons
} XRemoteViewID;

typedef struct XRemoteView XRemoteView;
typedef void (*XRemoteClearCallback)(void* context);
typedef void (*XRemoteViewDrawFunction)(Canvas*, XRemoteViewModel*);

typedef XRemoteView* (*XRemoteViewAllocator)(void* app_ctx);
typedef XRemoteView* (*XRemoteViewAllocator2)(void* app_ctx, void* model_ctx);

const char* xremote_button_get_name(int index);
int xremote_button_get_index(const char* name);

void xremote_canvas_draw_header(Canvas* canvas, ViewOrientation orient, const char* section);
void xremote_canvas_draw_exit_footer(Canvas* canvas, ViewOrientation orient, const char* text);
Expand All @@ -142,7 +150,7 @@ void xremote_canvas_draw_button_wide(
bool pressed,
uint8_t x,
uint8_t y,
char* text,
const char* text,
XRemoteIcon icon);
void xremote_canvas_draw_button_size(
Canvas* canvas,
Expand All @@ -162,14 +170,20 @@ void xremote_canvas_draw_frame(

XRemoteView*
xremote_view_alloc(void* app_ctx, ViewInputCallback input_cb, ViewDrawCallback draw_cb);
XRemoteView* xremote_view_alloc_empty();
void xremote_view_free(XRemoteView* rview);

InfraredRemoteButton* xremote_view_get_button_by_name(XRemoteView* rview, const char* name);
bool xremote_view_press_button(XRemoteView* rview, InfraredRemoteButton* button);
bool xremote_view_send_ir_msg_by_name(XRemoteView* rview, const char* name);

void xremote_view_set_context(XRemoteView* rview, void* context, XRemoteClearCallback on_clear);
void* xremote_view_get_context(XRemoteView* rview);
void xremote_view_model_context_set(XRemoteView* rview, void* model_ctx);
void xremote_view_clear_context(XRemoteView* rview);

void xremote_view_set_app_context(XRemoteView* rview, void* app_ctx);
void xremote_view_set_context(XRemoteView* rview, void* context, XRemoteClearCallback on_clear);
void xremote_view_set_view(XRemoteView* rview, View* view);

void* xremote_view_get_app_context(XRemoteView* rview);
void* xremote_view_get_context(XRemoteView* rview);
View* xremote_view_get_view(XRemoteView* rview);
Loading

0 comments on commit 73220eb

Please sign in to comment.