|
|
@@ -8,9 +8,23 @@
|
|
|
|
|
|
#include "xremote_app.h"
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+// XRemote generic functions and definitions
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
#define XREMOTE_APP_SETTINGS APP_DATA_PATH("xremote.cfg")
|
|
|
#define TAG "XRemoteApp"
|
|
|
|
|
|
+#define XREMOTE_ORIENTATION_TEXT_HORIZONTAL "Horizontal"
|
|
|
+#define XREMOTE_ORIENTATION_TEXT_VERTICAL "Vertical"
|
|
|
+#define XREMOTE_ORIENTATION_INDEX_HORIZONTAL 0
|
|
|
+#define XREMOTE_ORIENTATION_INDEX_VERTICAL 1
|
|
|
+
|
|
|
+#define XREMOTE_EXIT_BEHAVIOR_TEXT_PRESS "Press"
|
|
|
+#define XREMOTE_EXIT_BEHAVIOR_TEXT_HOLD "Hold"
|
|
|
+#define XREMOTE_EXIT_BEHAVIOR_INDEX_PRESS 0
|
|
|
+#define XREMOTE_EXIT_BEHAVIOR_INDEX_HOLD 1
|
|
|
+
|
|
|
const NotificationSequence g_sequence_blink_purple_50 = {
|
|
|
&message_red_255,
|
|
|
&message_blue_255,
|
|
|
@@ -18,6 +32,176 @@ const NotificationSequence g_sequence_blink_purple_50 = {
|
|
|
NULL,
|
|
|
};
|
|
|
|
|
|
+XRemoteAppExit xremote_app_get_exit_behavior(uint8_t exit_index) {
|
|
|
+ return exit_index ? XRemoteAppExitHold : XRemoteAppExitPress;
|
|
|
+}
|
|
|
+
|
|
|
+ViewOrientation xremote_app_get_orientation(uint8_t orientation_index) {
|
|
|
+ return orientation_index ? ViewOrientationVertical : ViewOrientationHorizontal;
|
|
|
+}
|
|
|
+
|
|
|
+const char* xremote_app_get_exit_str(XRemoteAppExit exit_behavior) {
|
|
|
+ return exit_behavior == XRemoteAppExitPress ? "Press" : "Hold";
|
|
|
+}
|
|
|
+
|
|
|
+const char* xremote_app_get_orientation_str(ViewOrientation view_orientation) {
|
|
|
+ return view_orientation == ViewOrientationHorizontal ? "Horizontal" : "Vertical";
|
|
|
+}
|
|
|
+
|
|
|
+uint32_t xremote_app_get_orientation_index(ViewOrientation view_orientation) {
|
|
|
+ return view_orientation == ViewOrientationHorizontal ? 0 : 1;
|
|
|
+}
|
|
|
+
|
|
|
+uint32_t xremote_app_get_exit_index(XRemoteAppExit exit_behavior) {
|
|
|
+ return exit_behavior == XRemoteAppExitPress ? 0 : 1;
|
|
|
+}
|
|
|
+
|
|
|
+void xremote_app_notification_blink(NotificationApp* notifications) {
|
|
|
+ xremote_app_assert_void(notifications);
|
|
|
+ notification_message(notifications, &g_sequence_blink_purple_50);
|
|
|
+}
|
|
|
+
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+// XRemote buttons and custom button pairs
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
+bool xremote_app_extension_load(XRemoteAppButtons* buttons, FuriString* path) {
|
|
|
+ Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
+ FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
|
|
+ FuriString* tmp = furi_string_alloc();
|
|
|
+ bool success = false;
|
|
|
+
|
|
|
+ do {
|
|
|
+ /* Open file and read the header */
|
|
|
+ if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(path))) break;
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_ok", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_ok, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_up", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_up, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_down", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_down, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_left", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_left, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_right", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_right, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_ok_hold", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_ok_hold, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_up_hold", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_up_hold, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_down_hold", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_down_hold, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_left_hold", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_left_hold, tmp);
|
|
|
+
|
|
|
+ if(!flipper_format_read_string(ff, "custom_right_hold", tmp)) break;
|
|
|
+ furi_string_set(buttons->custom_right_hold, tmp);
|
|
|
+
|
|
|
+ success = true;
|
|
|
+ } while(false);
|
|
|
+
|
|
|
+ furi_record_close(RECORD_STORAGE);
|
|
|
+ flipper_format_free(ff);
|
|
|
+ furi_string_free(tmp);
|
|
|
+
|
|
|
+ return success;
|
|
|
+}
|
|
|
+
|
|
|
+bool xremote_app_extension_store(XRemoteAppButtons* buttons, FuriString* path) {
|
|
|
+ Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
+ FlipperFormat* ff = flipper_format_file_alloc(storage);
|
|
|
+ bool success = false;
|
|
|
+
|
|
|
+ do {
|
|
|
+ if(!flipper_format_file_open_append(ff, furi_string_get_cstr(path))) break;
|
|
|
+ if(!flipper_format_write_comment_cstr(ff, "XRemote extension")) break;
|
|
|
+
|
|
|
+ if(!flipper_format_write_string(ff, "custom_ok", buttons->custom_ok)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_up", buttons->custom_up)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_down", buttons->custom_down)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_left", buttons->custom_left)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_right", buttons->custom_right)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_ok_hold", buttons->custom_ok_hold)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_up_hold", buttons->custom_up_hold)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_down_hold", buttons->custom_down_hold)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_left_hold", buttons->custom_left_hold)) break;
|
|
|
+ if(!flipper_format_write_string(ff, "custom_right_hold", buttons->custom_right_hold))
|
|
|
+ break;
|
|
|
+
|
|
|
+ success = true;
|
|
|
+ } while(false);
|
|
|
+
|
|
|
+ furi_record_close(RECORD_STORAGE);
|
|
|
+ flipper_format_free(ff);
|
|
|
+
|
|
|
+ return success;
|
|
|
+}
|
|
|
+
|
|
|
+void xremote_app_buttons_free(XRemoteAppButtons* buttons) {
|
|
|
+ xremote_app_assert_void(buttons);
|
|
|
+ infrared_remote_free(buttons->remote);
|
|
|
+ furi_string_free(buttons->custom_up);
|
|
|
+ furi_string_free(buttons->custom_down);
|
|
|
+ furi_string_free(buttons->custom_left);
|
|
|
+ furi_string_free(buttons->custom_right);
|
|
|
+ furi_string_free(buttons->custom_ok);
|
|
|
+ furi_string_free(buttons->custom_up_hold);
|
|
|
+ furi_string_free(buttons->custom_down_hold);
|
|
|
+ furi_string_free(buttons->custom_left_hold);
|
|
|
+ furi_string_free(buttons->custom_right_hold);
|
|
|
+ furi_string_free(buttons->custom_ok_hold);
|
|
|
+ free(buttons);
|
|
|
+}
|
|
|
+
|
|
|
+XRemoteAppButtons* xremote_app_buttons_alloc() {
|
|
|
+ XRemoteAppButtons* buttons = malloc(sizeof(XRemoteAppButtons));
|
|
|
+ buttons->remote = infrared_remote_alloc();
|
|
|
+ buttons->app_ctx = NULL;
|
|
|
+
|
|
|
+ /* Setup default buttons for custom layout */
|
|
|
+ buttons->custom_up = furi_string_alloc_set_str(XREMOTE_COMMAND_UP);
|
|
|
+ buttons->custom_down = furi_string_alloc_set_str(XREMOTE_COMMAND_DOWN);
|
|
|
+ buttons->custom_left = furi_string_alloc_set_str(XREMOTE_COMMAND_LEFT);
|
|
|
+ buttons->custom_right = furi_string_alloc_set_str(XREMOTE_COMMAND_RIGHT);
|
|
|
+ buttons->custom_ok = furi_string_alloc_set_str(XREMOTE_COMMAND_OK);
|
|
|
+ buttons->custom_up_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_INPUT);
|
|
|
+ buttons->custom_down_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_SETUP);
|
|
|
+ buttons->custom_left_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_MENU);
|
|
|
+ buttons->custom_right_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_LIST);
|
|
|
+ buttons->custom_ok_hold = furi_string_alloc_set_str(XREMOTE_COMMAND_POWER);
|
|
|
+
|
|
|
+ return buttons;
|
|
|
+}
|
|
|
+
|
|
|
+XRemoteAppButtons* xremote_app_buttons_load(XRemoteAppContext* app_ctx) {
|
|
|
+ /* 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;
|
|
|
+ XRemoteAppButtons* buttons = xremote_app_buttons_alloc();
|
|
|
+ buttons->app_ctx = app_ctx;
|
|
|
+
|
|
|
+ /* Load buttons from the selected path */
|
|
|
+ if(!infrared_remote_load(buttons->remote, app_ctx->file_path)) {
|
|
|
+ xremote_app_buttons_free(buttons);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Load custom buttons from the selected path */
|
|
|
+ xremote_app_extension_load(buttons, app_ctx->file_path);
|
|
|
+ return buttons;
|
|
|
+}
|
|
|
+
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+// XRemote application settings
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
XRemoteAppSettings* xremote_app_settings_alloc() {
|
|
|
XRemoteAppSettings* settings = malloc(sizeof(XRemoteAppSettings));
|
|
|
settings->orientation = ViewOrientationHorizontal;
|
|
|
@@ -45,10 +229,10 @@ bool xremote_app_settings_store(XRemoteAppSettings* settings) {
|
|
|
if(!flipper_format_write_comment_cstr(ff, "")) break;
|
|
|
|
|
|
/* Write actual configuration to the settings file */
|
|
|
- uint32_t value = settings->orientation == ViewOrientationHorizontal ? 0 : 1;
|
|
|
+ uint32_t value = xremote_app_get_orientation_index(settings->orientation);
|
|
|
if(!flipper_format_write_uint32(ff, "orientation", &value, 1)) break;
|
|
|
|
|
|
- value = settings->exit_behavior == XRemoteAppExitPress ? 0 : 1;
|
|
|
+ value = xremote_app_get_exit_index(settings->exit_behavior);
|
|
|
if(!flipper_format_write_uint32(ff, "appexit", &value, 1)) break;
|
|
|
|
|
|
value = settings->repeat_count;
|
|
|
@@ -81,9 +265,11 @@ bool xremote_app_settings_load(XRemoteAppSettings* settings) {
|
|
|
|
|
|
/* Parse config data from the buffer */
|
|
|
if(!flipper_format_read_uint32(ff, "orientation", &value, 1)) break;
|
|
|
- settings->orientation = value == 0 ? ViewOrientationHorizontal : ViewOrientationVertical;
|
|
|
+ settings->orientation = xremote_app_get_orientation(value);
|
|
|
+
|
|
|
if(!flipper_format_read_uint32(ff, "appexit", &value, 1)) break;
|
|
|
- settings->exit_behavior = value == 0 ? XRemoteAppExitPress : XRemoteAppExitHold;
|
|
|
+ settings->exit_behavior = xremote_app_get_exit_behavior(value);
|
|
|
+
|
|
|
if(!flipper_format_read_uint32(ff, "repeat", &value, 1)) break;
|
|
|
settings->repeat_count = value;
|
|
|
|
|
|
@@ -97,6 +283,10 @@ bool xremote_app_settings_load(XRemoteAppSettings* settings) {
|
|
|
return success;
|
|
|
}
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+// XRemote gloal context shared between every child application
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
XRemoteAppContext* xremote_app_context_alloc(void* arg) {
|
|
|
XRemoteAppContext* ctx = malloc(sizeof(XRemoteAppContext));
|
|
|
ctx->app_argument = arg;
|
|
|
@@ -136,14 +326,35 @@ void xremote_app_context_free(XRemoteAppContext* ctx) {
|
|
|
free(ctx);
|
|
|
}
|
|
|
|
|
|
-const char* xremote_app_context_get_exit_str(XRemoteAppContext* ctx) {
|
|
|
- XRemoteAppExit exit_behavior = ctx->app_settings->exit_behavior;
|
|
|
- return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";
|
|
|
+bool xremote_app_browser_select_file(XRemoteAppContext* app_ctx, const char* extension) {
|
|
|
+ DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
|
|
+ Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Open file browser (view and dialogs are managed by the browser itself) */
|
|
|
+ DialogsFileBrowserOptions browser;
|
|
|
+ dialog_file_browser_set_basic_options(&browser, extension, &I_IR_Icon_10x10);
|
|
|
+ browser.base_path = XREMOTE_APP_FOLDER;
|
|
|
+ FuriString* path = app_ctx->file_path;
|
|
|
+
|
|
|
+ /* Show file selection dialog (returns selected file path with file_path) */
|
|
|
+ bool status = dialog_file_browser_show(dialogs, path, path, &browser);
|
|
|
+
|
|
|
+ /* Cleanup file loading context */
|
|
|
+ furi_record_close(RECORD_STORAGE);
|
|
|
+ furi_record_close(RECORD_DIALOGS);
|
|
|
+
|
|
|
+ return status;
|
|
|
}
|
|
|
|
|
|
-void xremote_app_notification_blink(NotificationApp* notifications) {
|
|
|
- xremote_app_assert_void(notifications);
|
|
|
- notification_message(notifications, &g_sequence_blink_purple_50);
|
|
|
+const char* xremote_app_context_get_exit_str(XRemoteAppContext* app_ctx) {
|
|
|
+ XRemoteAppExit exit_behavior = app_ctx->app_settings->exit_behavior;
|
|
|
+ return exit_behavior == XRemoteAppExitHold ? "Hold to exit" : "Press to exit";
|
|
|
}
|
|
|
|
|
|
void xremote_app_context_notify_led(XRemoteAppContext* app_ctx) {
|
|
|
@@ -159,6 +370,10 @@ bool xremote_app_send_signal(XRemoteAppContext* app_ctx, InfraredSignal* signal)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+// XRemote application factory
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
void xremote_app_view_alloc(XRemoteApp* app, uint32_t view_id, XRemoteViewAllocator allocator) {
|
|
|
furi_assert(app);
|
|
|
xremote_app_assert_void(app->app_ctx);
|
|
|
@@ -175,6 +390,26 @@ void xremote_app_view_alloc(XRemoteApp* app, uint32_t view_id, XRemoteViewAlloca
|
|
|
view_dispatcher_add_view(view_disp, app->view_id, app_view);
|
|
|
}
|
|
|
|
|
|
+void xremote_app_view_alloc2(
|
|
|
+ XRemoteApp* app,
|
|
|
+ uint32_t view_id,
|
|
|
+ XRemoteViewAllocator2 allocator,
|
|
|
+ void* model_ctx) {
|
|
|
+ furi_assert(app);
|
|
|
+ xremote_app_assert_void(app->app_ctx);
|
|
|
+
|
|
|
+ if(app->view_id == view_id && app->view_ctx != NULL) return;
|
|
|
+
|
|
|
+ xremote_app_view_free(app);
|
|
|
+ app->view_id = view_id;
|
|
|
+
|
|
|
+ app->view_ctx = allocator(app->app_ctx, model_ctx);
|
|
|
+ View* app_view = xremote_view_get_view(app->view_ctx);
|
|
|
+
|
|
|
+ ViewDispatcher* view_disp = app->app_ctx->view_dispatcher;
|
|
|
+ view_dispatcher_add_view(view_disp, app->view_id, app_view);
|
|
|
+}
|
|
|
+
|
|
|
void xremote_app_view_free(XRemoteApp* app) {
|
|
|
xremote_app_assert_void(app);
|
|
|
|
|
|
@@ -286,7 +521,10 @@ void xremote_app_user_context_free(XRemoteApp* app) {
|
|
|
|
|
|
XRemoteApp* xremote_app_alloc(XRemoteAppContext* ctx) {
|
|
|
furi_assert(ctx);
|
|
|
+
|
|
|
XRemoteApp* app = malloc(sizeof(XRemoteApp));
|
|
|
+ xremote_app_assert(app, NULL);
|
|
|
+
|
|
|
app->submenu_id = XRemoteViewNone;
|
|
|
app->view_id = XRemoteViewNone;
|
|
|
app->app_ctx = ctx;
|
|
|
@@ -294,6 +532,7 @@ XRemoteApp* xremote_app_alloc(XRemoteAppContext* ctx) {
|
|
|
app->view_ctx = NULL;
|
|
|
app->on_clear = NULL;
|
|
|
app->context = NULL;
|
|
|
+
|
|
|
return app;
|
|
|
}
|
|
|
|