From c997e9a819b615520eea979dde0ae3652423a50a Mon Sep 17 00:00:00 2001 From: ESurge Date: Mon, 19 Sep 2022 19:37:56 -0700 Subject: [PATCH] Added iButton and Clock to Main Menu but they are loaded via FAP Loader. --- applications/main/application.fam | 2 + .../main/clock_loader/application.fam | 10 + .../main/clock_loader/clock_loader_app.c | 179 ++++++++++++++++++ .../main/ibutton_loader/application.fam | 13 ++ .../main/ibutton_loader/ibutton_loader_app.c | 179 ++++++++++++++++++ 5 files changed, 383 insertions(+) create mode 100644 applications/main/clock_loader/application.fam create mode 100644 applications/main/clock_loader/clock_loader_app.c create mode 100644 applications/main/ibutton_loader/application.fam create mode 100644 applications/main/ibutton_loader/ibutton_loader_app.c diff --git a/applications/main/application.fam b/applications/main/application.fam index 3bdec8aa6..db9c507f5 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -3,8 +3,10 @@ App( name="Basic applications for main menu", apptype=FlipperAppType.METAPACKAGE, provides=[ + "clock_loader", "gpio", # "ibutton", + "ibutton_loader", "infrared", "lfrfid", "nfc", diff --git a/applications/main/clock_loader/application.fam b/applications/main/clock_loader/application.fam new file mode 100644 index 000000000..703c78f63 --- /dev/null +++ b/applications/main/clock_loader/application.fam @@ -0,0 +1,10 @@ +App( + appid="clock_loader", + name="Clock", + apptype=FlipperAppType.APP, + entry_point="clock_loader_app", + requires=["gui"], + stack_size=int(1.5 * 1024), + icon="A_Clock_14", + order=9, +) diff --git a/applications/main/clock_loader/clock_loader_app.c b/applications/main/clock_loader/clock_loader_app.c new file mode 100644 index 000000000..c4579bdb2 --- /dev/null +++ b/applications/main/clock_loader/clock_loader_app.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "clock_loader_app" + +typedef struct { + FlipperApplication* app; + Storage* storage; + DialogsApp* dialogs; + Gui* gui; + string_t fap_path; +} FapLoader; + +static bool + fap_loader_item_callback(string_t path, void* context, uint8_t** icon_ptr, string_t item_name) { + FapLoader* loader = context; + furi_assert(loader); + + FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(app, string_get_cstr(path)); + + bool load_success = false; + + if(preload_res == FlipperApplicationPreloadStatusSuccess) { + const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); + if(manifest->has_icon) { + memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE); + } + string_set_str(item_name, manifest->name); + load_success = true; + } else { + FURI_LOG_E(TAG, "FAP Loader failed to preload %s", string_get_cstr(path)); + load_success = false; + } + + flipper_application_free(app); + return load_success; +} + +static bool fap_loader_run_selected_app(FapLoader* loader) { + furi_assert(loader); + + string_t error_message; + + string_init_set(error_message, "unknown error"); + + bool file_selected = false; + bool show_error = true; + do { + file_selected = true; + loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + + FURI_LOG_I(TAG, "FAP Loader is loading %s", string_get_cstr(loader->fap_path)); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(loader->app, string_get_cstr(loader->fap_path)); + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + const char* err_msg = flipper_application_preload_status_to_string(preload_res); + string_printf(error_message, "Preload failed: %s", err_msg); + FURI_LOG_E( + TAG, + "FAP Loader failed to preload %s: %s", + string_get_cstr(loader->fap_path), + err_msg); + break; + } + + FURI_LOG_I(TAG, "FAP Loader is mapping"); + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app); + if(load_status != FlipperApplicationLoadStatusSuccess) { + const char* err_msg = flipper_application_load_status_to_string(load_status); + string_printf(error_message, "Load failed: %s", err_msg); + FURI_LOG_E( + TAG, + "FAP Loader failed to map to memory %s: %s", + string_get_cstr(loader->fap_path), + err_msg); + break; + } + + FURI_LOG_I(TAG, "FAP Loader is staring app"); + + FuriThread* thread = flipper_application_spawn(loader->app, NULL); + furi_thread_start(thread); + furi_thread_join(thread); + + show_error = false; + int ret = furi_thread_get_return_code(thread); + + FURI_LOG_I(TAG, "FAP app returned: %i", ret); + } while(0); + + if(show_error) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + + string_t buffer; + string_init(buffer); + string_printf(buffer, "%s", string_get_cstr(error_message)); + string_replace_str(buffer, ":", "\n"); + dialog_message_set_text( + message, string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); + + dialog_message_show(loader->dialogs, message); + dialog_message_free(message); + string_clear(buffer); + } + + string_clear(error_message); + + if(file_selected) { + flipper_application_free(loader->app); + } + + return file_selected; +} + +static bool fap_loader_select_app(FapLoader* loader) { + const DialogsFileBrowserOptions browser_options = { + .extension = ".fap", + .skip_assets = true, + .icon = &I_badusb_10px, + .hide_ext = true, + .item_loader_callback = fap_loader_item_callback, + .item_loader_context = loader, + }; + + return dialog_file_browser_show( + loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); +} + +int32_t clock_loader_app(void* p) { + UNUSED(p); + FapLoader* loader = malloc(sizeof(FapLoader)); + loader->storage = furi_record_open(RECORD_STORAGE); + loader->dialogs = furi_record_open(RECORD_DIALOGS); + loader->gui = furi_record_open(RECORD_GUI); + + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + Loading* loading = loading_alloc(); + + view_dispatcher_enable_queue(view_dispatcher); + view_dispatcher_attach_to_gui(view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); + + const char* file = "/ext/apps/Main/Clock.fap"; + + if(file) { + string_init_set(loader->fap_path, file); + fap_loader_run_selected_app(loader); + } else { + string_init_set(loader->fap_path, EXT_PATH("apps")); + + while(fap_loader_select_app(loader)) { + view_dispatcher_switch_to_view(view_dispatcher, 0); + fap_loader_run_selected_app(loader); + }; + } + + view_dispatcher_remove_view(view_dispatcher, 0); + loading_free(loading); + view_dispatcher_free(view_dispatcher); + + string_clear(loader->fap_path); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + free(loader); + return 0; +} \ No newline at end of file diff --git a/applications/main/ibutton_loader/application.fam b/applications/main/ibutton_loader/application.fam new file mode 100644 index 000000000..20663fea0 --- /dev/null +++ b/applications/main/ibutton_loader/application.fam @@ -0,0 +1,13 @@ +App( + appid="ibutton_loader", + name="iButton", + apptype=FlipperAppType.APP, + entry_point="ibutton_loader_app", + requires=[ + "gui", + "storage", + ], + stack_size=int(1.5 * 1024), + icon="A_iButton_14", + order=60, +) diff --git a/applications/main/ibutton_loader/ibutton_loader_app.c b/applications/main/ibutton_loader/ibutton_loader_app.c new file mode 100644 index 000000000..6eb071603 --- /dev/null +++ b/applications/main/ibutton_loader/ibutton_loader_app.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "ibutton_loader_app" + +typedef struct { + FlipperApplication* app; + Storage* storage; + DialogsApp* dialogs; + Gui* gui; + string_t fap_path; +} FapLoader; + +static bool + fap_loader_item_callback(string_t path, void* context, uint8_t** icon_ptr, string_t item_name) { + FapLoader* loader = context; + furi_assert(loader); + + FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(app, string_get_cstr(path)); + + bool load_success = false; + + if(preload_res == FlipperApplicationPreloadStatusSuccess) { + const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); + if(manifest->has_icon) { + memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE); + } + string_set_str(item_name, manifest->name); + load_success = true; + } else { + FURI_LOG_E(TAG, "FAP Loader failed to preload %s", string_get_cstr(path)); + load_success = false; + } + + flipper_application_free(app); + return load_success; +} + +static bool fap_loader_run_selected_app(FapLoader* loader) { + furi_assert(loader); + + string_t error_message; + + string_init_set(error_message, "unknown error"); + + bool file_selected = false; + bool show_error = true; + do { + file_selected = true; + loader->app = flipper_application_alloc(loader->storage, &hashtable_api_interface); + + FURI_LOG_I(TAG, "FAP Loader is loading %s", string_get_cstr(loader->fap_path)); + + FlipperApplicationPreloadStatus preload_res = + flipper_application_preload(loader->app, string_get_cstr(loader->fap_path)); + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + const char* err_msg = flipper_application_preload_status_to_string(preload_res); + string_printf(error_message, "Preload failed: %s", err_msg); + FURI_LOG_E( + TAG, + "FAP Loader failed to preload %s: %s", + string_get_cstr(loader->fap_path), + err_msg); + break; + } + + FURI_LOG_I(TAG, "FAP Loader is mapping"); + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app); + if(load_status != FlipperApplicationLoadStatusSuccess) { + const char* err_msg = flipper_application_load_status_to_string(load_status); + string_printf(error_message, "Load failed: %s", err_msg); + FURI_LOG_E( + TAG, + "FAP Loader failed to map to memory %s: %s", + string_get_cstr(loader->fap_path), + err_msg); + break; + } + + FURI_LOG_I(TAG, "FAP Loader is staring app"); + + FuriThread* thread = flipper_application_spawn(loader->app, NULL); + furi_thread_start(thread); + furi_thread_join(thread); + + show_error = false; + int ret = furi_thread_get_return_code(thread); + + FURI_LOG_I(TAG, "FAP app returned: %i", ret); + } while(0); + + if(show_error) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + + string_t buffer; + string_init(buffer); + string_printf(buffer, "%s", string_get_cstr(error_message)); + string_replace_str(buffer, ":", "\n"); + dialog_message_set_text( + message, string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); + + dialog_message_show(loader->dialogs, message); + dialog_message_free(message); + string_clear(buffer); + } + + string_clear(error_message); + + if(file_selected) { + flipper_application_free(loader->app); + } + + return file_selected; +} + +static bool fap_loader_select_app(FapLoader* loader) { + const DialogsFileBrowserOptions browser_options = { + .extension = ".fap", + .skip_assets = true, + .icon = &I_badusb_10px, + .hide_ext = true, + .item_loader_callback = fap_loader_item_callback, + .item_loader_context = loader, + }; + + return dialog_file_browser_show( + loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); +} + +int32_t ibutton_loader_app(void* p) { + UNUSED(p); + FapLoader* loader = malloc(sizeof(FapLoader)); + loader->storage = furi_record_open(RECORD_STORAGE); + loader->dialogs = furi_record_open(RECORD_DIALOGS); + loader->gui = furi_record_open(RECORD_GUI); + + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + Loading* loading = loading_alloc(); + + view_dispatcher_enable_queue(view_dispatcher); + view_dispatcher_attach_to_gui(view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(view_dispatcher, 0, loading_get_view(loading)); + + const char* file = "/ext/apps/Main/ibutton.fap"; + + if(file) { + string_init_set(loader->fap_path, file); + fap_loader_run_selected_app(loader); + } else { + string_init_set(loader->fap_path, EXT_PATH("apps")); + + while(fap_loader_select_app(loader)) { + view_dispatcher_switch_to_view(view_dispatcher, 0); + fap_loader_run_selected_app(loader); + }; + } + + view_dispatcher_remove_view(view_dispatcher, 0); + loading_free(loading); + view_dispatcher_free(view_dispatcher); + + string_clear(loader->fap_path); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); + free(loader); + return 0; +} \ No newline at end of file