Skip to content

Commit 73220eb

Browse files
committed
Implemented custom layout page and button editing menu (#4)
* 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
1 parent c26ea5c commit 73220eb

24 files changed

+922
-188
lines changed

README.md

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
# flipper-xremote
2-
Advanced IR Remote App for Flipper Device
32

4-
## About
3+
Advanced IR Remote App for Flipper Device
4+
5+
## Idea
6+
57
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.
68

9+
## Learn new remote
10+
711
`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.
812

13+
## Custom Layout
14+
15+
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.
16+
17+
<table align="center">
18+
<tr>
19+
<td align="center">Edit custom page buttons</td>
20+
</tr>
21+
<tr>
22+
<td><img src="https://github.com/kala13x/flipper-xremote/blob/main/screens/custom_layout.png" alt="XRemote edit layout"></td>
23+
</tr>
24+
</table>
25+
26+
## Standard file support
27+
928
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.
1029

1130
Button name | Description
@@ -43,21 +62,21 @@ Button name | Description
4362
- [x] Learn new remote
4463
- [x] Signal analyzer
4564
- [x] Use saved remote
46-
- [x] General button page
47-
- [x] Control buttons page
48-
- [x] Navigation buttons page
49-
- [x] Player buttons page
50-
- [ ] Custom buttons page
51-
- [ ] Full button list
52-
- [ ] Rename remote file
53-
- [ ] Delete remote file
65+
- [x] General button page
66+
- [x] Control buttons page
67+
- [x] Navigation buttons page
68+
- [x] Player buttons page
69+
- [x] Custom buttons page
70+
- [x] Edit custom layout
71+
- [ ] Add or remove button
72+
- [ ] All buttons page
5473
- [x] Application settings
55-
- [x] GUI to change settings
56-
- [x] Load settings from the file
57-
- [x] Store settings to the file
58-
- [x] Vertical/horizontal views
59-
- [x] IR command repeat count
60-
- [x] Exit button behavior
74+
- [x] GUI to change settings
75+
- [x] Load settings from the file
76+
- [x] Store settings to the file
77+
- [x] Vertical/horizontal views
78+
- [x] IR command repeat count
79+
- [x] Exit button behavior
6180

6281
## Screens
6382

application.fam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ App(
66
requires=["gui", "dialogs", "infrared"],
77
stack_size=3 * 1024,
88
order=1,
9-
fap_version="1.0",
9+
fap_version="1.1",
1010
fap_category="Infrared",
1111
fap_icon_assets="assets",
1212
fap_icon_assets_symbol="xc",

deploy.sh

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,38 @@
22
# This source is part of "flipper-xremote" project
33
# 2023 - Sandro Kalatozishvili (s.kalatoz@gmail.com)
44

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

8+
XCLR_DIM="\x1B[2m"
9+
XCLR_RED="\x1B[31m"
10+
XCLR_RESET="\x1B[0m\n"
11+
12+
# Parse firmware path from arguments if present
13+
for arg in "$@"; do
14+
if [[ $arg == --firmware=* || $arg == --fw=* ]]; then
15+
FLIPPER_FIRMWARE="${arg#*=}"
16+
fi
17+
done
18+
19+
# Check if FLIPPER_FIRMWARE variable is set
20+
if [ -z "$FLIPPER_FIRMWARE" ]; then
21+
echo -e "$XCLR_RED""FLIPPER_FIRMWARE variable is not set or is empty. $XCLR_RESET"
22+
echo "You can either export FLIPPER_FIRMWARE variable:"
23+
echo -e "$XCLR_DIM""export FLIPPER_FIRMWARE=/path/to/firmware $XCLR_RESET"
24+
echo "Or pass the firmware path as an argument:"
25+
echo -e "$XCLR_DIM""$0 --fw=/path/to/firmware $XCLR_RESET"
26+
exit 1
27+
else
28+
echo "Using firmware path: $FLIPPER_FIRMWARE"
29+
fi
30+
31+
# Check if the path exists and has a applications_user sub directory
32+
if [[ ! -d "$FLIPPER_FIRMWARE" || ! -d "$FLIPPER_FIRMWARE/applications_user" || ! -f "$FLIPPER_FIRMWARE/fbt" ]]; then
33+
echo -e "$XCLR_RED""Firmware path does not exist or does not contain the required flipper context. $XCLR_RESET"
34+
exit 1
35+
fi
36+
937
# Private variables
1038
XREMOTE_PROJ_PATH=$(dirname $(readlink -f "$0"))
1139
XREMOTE_PROJ_NAME=$(basename "$XREMOTE_PROJ_PATH")
@@ -28,3 +56,6 @@ for arg in "$@"; do
2856
[ $DEPLOY_DONE -eq 1 ] && sudo qflipper
2957
fi
3058
done
59+
60+
# Return with success
61+
exit 0

docs/README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1-
## flipper-xremote
2-
Advanced IR Remote App for Flipper Device
1+
# flipper-xremote
32

4-
## About
5-
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.
3+
Advanced IR Remote App for Flipper Device
64

7-
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.
5+
## Idea
86

9-
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.
7+
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.
8+
9+
## Learn new remote
10+
11+
`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.
12+
13+
## Custom Layout
14+
15+
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.
16+
17+
## Standard file support
18+
19+
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.
1020

1121
## Button schema
22+
1223
Button name | Description
1324
------------|-------------------
1425
Power | Power
@@ -37,4 +48,3 @@ Play_pa | Play/Pause
3748
Pause | Pause
3849
Play | Play
3950
Stop | Stop
40-

docs/changelog.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
## v1.1
2+
3+
Custom layout and bug fixing
4+
5+
- Implemented custom layout page and button editor
6+
- Updated exit button behavior on general page
7+
- Fixed crash on dev branch firmware builds
8+
- Refactored the codebase and fixed bugs
9+
110
## v1.0
211

312
First stable release
13+
414
- Adjusted layout of remote apps
515
- Added learn mode support
616
- Added signal analyzer app
@@ -9,7 +19,8 @@ First stable release
919
## v0.9
1020

1121
First beta release
22+
1223
- Stable saved remote control apps
1324
- Flipper standard .ir file support
1425
- Horizontal/Vertical view support for all apps
15-
- Settings variable item list and functionality
26+
- Settings variable item list and functionality

screens/custom_layout.png

6.29 KB
Loading

screens/saved_remote_menu.png

3.66 KB
Loading

screens/settings_menu.png

259 Bytes
Loading

views/xremote_common_view.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,29 @@ const char* xremote_button_get_name(int index) {
4848
return g_buttons[index].name;
4949
}
5050

51+
int xremote_button_get_index(const char* name) {
52+
size_t i;
53+
for(i = 0; i < XREMOTE_BUTTON_COUNT; i++) {
54+
if(!strcmp(name, g_buttons[i].name)) return g_buttons[i].index;
55+
}
56+
return -1;
57+
}
58+
5159
struct XRemoteView {
5260
XRemoteClearCallback on_clear;
5361
XRemoteAppContext* app_ctx;
5462
View* view;
5563
void* context;
5664
};
5765

66+
XRemoteView* xremote_view_alloc_empty() {
67+
XRemoteView* remote_view = malloc(sizeof(XRemoteView));
68+
return remote_view;
69+
}
70+
5871
XRemoteView*
5972
xremote_view_alloc(void* app_ctx, ViewInputCallback input_cb, ViewDrawCallback draw_cb) {
60-
XRemoteView* remote_view = malloc(sizeof(XRemoteView));
73+
XRemoteView* remote_view = xremote_view_alloc_empty();
6174
remote_view->app_ctx = app_ctx;
6275
remote_view->view = view_alloc();
6376

@@ -89,11 +102,21 @@ void xremote_view_set_context(XRemoteView* rview, void* context, XRemoteClearCal
89102
rview->on_clear = on_clear;
90103
}
91104

105+
void xremote_view_set_view(XRemoteView* rview, View* view) {
106+
xremote_view_clear_context(rview);
107+
rview->view = view;
108+
}
109+
92110
void* xremote_view_get_context(XRemoteView* rview) {
93111
furi_assert(rview);
94112
return rview->context;
95113
}
96114

115+
void xremote_view_set_app_context(XRemoteView* rview, void* app_ctx) {
116+
furi_assert(rview);
117+
rview->app_ctx = app_ctx;
118+
}
119+
97120
void* xremote_view_get_app_context(XRemoteView* rview) {
98121
furi_assert(rview);
99122
return rview->app_ctx;
@@ -113,8 +136,8 @@ View* xremote_view_get_view(XRemoteView* rview) {
113136

114137
InfraredRemoteButton* xremote_view_get_button_by_name(XRemoteView* rview, const char* name) {
115138
xremote_app_assert(rview->context, NULL);
116-
InfraredRemote* remote = (InfraredRemote*)rview->context;
117-
return infrared_remote_get_button_by_name(remote, name);
139+
XRemoteAppButtons* buttons = (XRemoteAppButtons*)rview->context;
140+
return infrared_remote_get_button_by_name(buttons->remote, name);
118141
}
119142

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

161+
void xremote_view_model_context_set(XRemoteView* rview, void* model_ctx) {
162+
with_view_model(
163+
xremote_view_get_view(rview),
164+
XRemoteViewModel * model,
165+
{
166+
model->context = model_ctx;
167+
model->up_pressed = false;
168+
model->down_pressed = false;
169+
model->left_pressed = false;
170+
model->right_pressed = false;
171+
model->back_pressed = false;
172+
model->ok_pressed = false;
173+
model->hold = false;
174+
},
175+
true);
176+
}
177+
138178
void xremote_canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, XRemoteIcon icon) {
139179
if(icon == XRemoteIconEnter) {
140180
canvas_draw_circle(canvas, x - 2, y, 4);
@@ -261,7 +301,7 @@ void xremote_canvas_draw_button_wide(
261301
bool pressed,
262302
uint8_t x,
263303
uint8_t y,
264-
char* text,
304+
const char* text,
265305
XRemoteIcon icon) {
266306
elements_slightly_rounded_frame(canvas, x + 4, y, 56, 15);
267307

views/xremote_common_view.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "../infrared/infrared_remote.h"
2222

2323
#define XREMOTE_BUTTON_COUNT 26
24+
#define XREMOTE_NAME_MAX 16
25+
2426
#define XREMOTE_COMMAND_POWER "Power"
2527
#define XREMOTE_COMMAND_SETUP "Setup"
2628
#define XREMOTE_COMMAND_INPUT "Input"
@@ -82,13 +84,14 @@ typedef enum {
8284
} XRemoteIcon;
8385

8486
typedef struct {
87+
void* context;
8588
bool ok_pressed;
8689
bool back_pressed;
8790
bool up_pressed;
8891
bool down_pressed;
8992
bool left_pressed;
9093
bool right_pressed;
91-
void* context;
94+
bool hold;
9295
} XRemoteViewModel;
9396

9497
typedef enum {
@@ -109,17 +112,22 @@ typedef enum {
109112
XRemoteViewIRSubmenu,
110113
XRemoteViewIRGeneral,
111114
XRemoteViewIRControl,
115+
XRemoteViewIRPlayback,
112116
XRemoteViewIRNavigation,
113-
XRemoteViewIRPlayer,
114-
XRemoteViewIRCustom
117+
XRemoteViewIRCustomPage,
118+
XRemoteViewIRCustomEditPage,
119+
XRemoteViewIRAllButtons
115120
} XRemoteViewID;
116121

117122
typedef struct XRemoteView XRemoteView;
118123
typedef void (*XRemoteClearCallback)(void* context);
119124
typedef void (*XRemoteViewDrawFunction)(Canvas*, XRemoteViewModel*);
125+
120126
typedef XRemoteView* (*XRemoteViewAllocator)(void* app_ctx);
127+
typedef XRemoteView* (*XRemoteViewAllocator2)(void* app_ctx, void* model_ctx);
121128

122129
const char* xremote_button_get_name(int index);
130+
int xremote_button_get_index(const char* name);
123131

124132
void xremote_canvas_draw_header(Canvas* canvas, ViewOrientation orient, const char* section);
125133
void xremote_canvas_draw_exit_footer(Canvas* canvas, ViewOrientation orient, const char* text);
@@ -142,7 +150,7 @@ void xremote_canvas_draw_button_wide(
142150
bool pressed,
143151
uint8_t x,
144152
uint8_t y,
145-
char* text,
153+
const char* text,
146154
XRemoteIcon icon);
147155
void xremote_canvas_draw_button_size(
148156
Canvas* canvas,
@@ -162,14 +170,20 @@ void xremote_canvas_draw_frame(
162170

163171
XRemoteView*
164172
xremote_view_alloc(void* app_ctx, ViewInputCallback input_cb, ViewDrawCallback draw_cb);
173+
XRemoteView* xremote_view_alloc_empty();
165174
void xremote_view_free(XRemoteView* rview);
166175

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

171-
void xremote_view_set_context(XRemoteView* rview, void* context, XRemoteClearCallback on_clear);
172-
void* xremote_view_get_context(XRemoteView* rview);
180+
void xremote_view_model_context_set(XRemoteView* rview, void* model_ctx);
173181
void xremote_view_clear_context(XRemoteView* rview);
182+
183+
void xremote_view_set_app_context(XRemoteView* rview, void* app_ctx);
184+
void xremote_view_set_context(XRemoteView* rview, void* context, XRemoteClearCallback on_clear);
185+
void xremote_view_set_view(XRemoteView* rview, View* view);
186+
174187
void* xremote_view_get_app_context(XRemoteView* rview);
188+
void* xremote_view_get_context(XRemoteView* rview);
175189
View* xremote_view_get_view(XRemoteView* rview);

0 commit comments

Comments
 (0)