[FL-2837][FL-3270] Loader refaptoring: second encounter (#2779)
* Core: rename internal FlipperApplication to FlipperInternalApplication * FAP Loader: move load_name_and_icon to flipper_application library * Loader menu: rework api * View holder: move to gui service * Loader: simple "loading" worker * Loader: applications dialog * Loader: fapping * Update f18 api * Apps: remove fap_loader * Libs, flipper application: store args, rename thread allocation * Loader: error handling * Apps: use loader error handling * Loader: documentation * FBT: accomodate loader * Loader: do not raise gui error if loader is locked * Archive: accomodate loader * Loader: fix loading message * Flipper: drop some old dolphin legacy * Loader: generalize error construction Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									4ddfe05a59
								
							
						
					
					
						commit
						761a14e6e2
					
				| @ -26,7 +26,6 @@ Applications for main Flipper menu. | |||||||
| 
 | 
 | ||||||
| - `archive`             - Archive and file manager  | - `archive`             - Archive and file manager  | ||||||
| - `bad_usb`             - Bad USB application | - `bad_usb`             - Bad USB application | ||||||
| - `fap_loader`          - External applications loader |  | ||||||
| - `gpio`                - GPIO application: includes USART bridge and GPIO control | - `gpio`                - GPIO application: includes USART bridge and GPIO control | ||||||
| - `ibutton`             - iButton application, onewire keys and more | - `ibutton`             - iButton application, onewire keys and more | ||||||
| - `infrared`            - Infrared application, controls your IR devices | - `infrared`            - Infrared application, controls your IR devices | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ App( | |||||||
|         "subghz", |         "subghz", | ||||||
|         "bad_usb", |         "bad_usb", | ||||||
|         "u2f", |         "u2f", | ||||||
|         "fap_loader", |  | ||||||
|         "archive", |         "archive", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
| #include <core/common_defines.h> | #include <core/common_defines.h> | ||||||
| #include <core/log.h> | #include <core/log.h> | ||||||
| #include <gui/modules/file_browser_worker.h> | #include <gui/modules/file_browser_worker.h> | ||||||
| #include <fap_loader/fap_loader_app.h> | #include <flipper_application/flipper_application.h> | ||||||
| #include <math.h> | #include <math.h> | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
| @ -367,7 +367,7 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { | |||||||
| static bool archive_get_fap_meta(FuriString* file_path, FuriString* fap_name, uint8_t** icon_ptr) { | static bool archive_get_fap_meta(FuriString* file_path, FuriString* fap_name, uint8_t** icon_ptr) { | ||||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); |     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|     bool success = false; |     bool success = false; | ||||||
|     if(fap_loader_load_name_and_icon(file_path, storage, icon_ptr, fap_name)) { |     if(flipper_application_load_name_and_icon(file_path, storage, icon_ptr, fap_name)) { | ||||||
|         success = true; |         success = true; | ||||||
|     } |     } | ||||||
|     furi_record_close(RECORD_STORAGE); |     furi_record_close(RECORD_STORAGE); | ||||||
|  | |||||||
| @ -11,17 +11,28 @@ | |||||||
| #define SCENE_STATE_DEFAULT (0) | #define SCENE_STATE_DEFAULT (0) | ||||||
| #define SCENE_STATE_NEED_REFRESH (1) | #define SCENE_STATE_NEED_REFRESH (1) | ||||||
| 
 | 
 | ||||||
| static const char* flipper_app_name[] = { | const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { | ||||||
|     [ArchiveFileTypeIButton] = "iButton", |     switch(file_type) { | ||||||
|     [ArchiveFileTypeNFC] = "NFC", |     case ArchiveFileTypeIButton: | ||||||
|     [ArchiveFileTypeSubGhz] = "Sub-GHz", |         return "iButton"; | ||||||
|     [ArchiveFileTypeLFRFID] = "125 kHz RFID", |     case ArchiveFileTypeNFC: | ||||||
|     [ArchiveFileTypeInfrared] = "Infrared", |         return "NFC"; | ||||||
|     [ArchiveFileTypeBadUsb] = "Bad USB", |     case ArchiveFileTypeSubGhz: | ||||||
|     [ArchiveFileTypeU2f] = "U2F", |         return "Sub-GHz"; | ||||||
|     [ArchiveFileTypeUpdateManifest] = "UpdaterApp", |     case ArchiveFileTypeLFRFID: | ||||||
|     [ArchiveFileTypeApplication] = "Applications", |         return "125 kHz RFID"; | ||||||
| }; |     case ArchiveFileTypeInfrared: | ||||||
|  |         return "Infrared"; | ||||||
|  |     case ArchiveFileTypeBadUsb: | ||||||
|  |         return "Bad USB"; | ||||||
|  |     case ArchiveFileTypeU2f: | ||||||
|  |         return "U2F"; | ||||||
|  |     case ArchiveFileTypeUpdateManifest: | ||||||
|  |         return "UpdaterApp"; | ||||||
|  |     default: | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| static void archive_loader_callback(const void* message, void* context) { | static void archive_loader_callback(const void* message, void* context) { | ||||||
|     furi_assert(message); |     furi_assert(message); | ||||||
| @ -39,20 +50,20 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec | |||||||
|     UNUSED(browser); |     UNUSED(browser); | ||||||
|     Loader* loader = furi_record_open(RECORD_LOADER); |     Loader* loader = furi_record_open(RECORD_LOADER); | ||||||
| 
 | 
 | ||||||
|     LoaderStatus status; |     const char* app_name = archive_get_flipper_app_name(selected->type); | ||||||
|     if(selected->is_app) { |  | ||||||
|         char* param = strrchr(furi_string_get_cstr(selected->path), '/'); |  | ||||||
|         if(param != NULL) { |  | ||||||
|             param++; |  | ||||||
|         } |  | ||||||
|         status = loader_start(loader, flipper_app_name[selected->type], param); |  | ||||||
|     } else { |  | ||||||
|         status = loader_start( |  | ||||||
|             loader, flipper_app_name[selected->type], furi_string_get_cstr(selected->path)); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if(status != LoaderStatusOk) { |     if(app_name) { | ||||||
|         FURI_LOG_E(TAG, "loader_start failed: %d", status); |         if(selected->is_app) { | ||||||
|  |             char* param = strrchr(furi_string_get_cstr(selected->path), '/'); | ||||||
|  |             if(param != NULL) { | ||||||
|  |                 param++; | ||||||
|  |             } | ||||||
|  |             loader_start_with_gui_error(loader, app_name, param); | ||||||
|  |         } else { | ||||||
|  |             loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path)); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     furi_record_close(RECORD_LOADER); |     furi_record_close(RECORD_LOADER); | ||||||
|  | |||||||
| @ -1,15 +0,0 @@ | |||||||
| App( |  | ||||||
|     appid="fap_loader", |  | ||||||
|     name="Applications", |  | ||||||
|     apptype=FlipperAppType.APP, |  | ||||||
|     entry_point="fap_loader_app", |  | ||||||
|     cdefines=["APP_FAP_LOADER"], |  | ||||||
|     requires=[ |  | ||||||
|         "gui", |  | ||||||
|         "storage", |  | ||||||
|         "loader", |  | ||||||
|     ], |  | ||||||
|     stack_size=int(1.5 * 1024), |  | ||||||
|     icon="A_Plugins_14", |  | ||||||
|     order=90, |  | ||||||
| ) |  | ||||||
| @ -1,216 +0,0 @@ | |||||||
| #include "fap_loader_app.h" |  | ||||||
| 
 |  | ||||||
| #include <furi.h> |  | ||||||
| #include <furi_hal_debug.h> |  | ||||||
| 
 |  | ||||||
| #include <assets_icons.h> |  | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <gui/view_dispatcher.h> |  | ||||||
| #include <gui/modules/loading.h> |  | ||||||
| #include <dialogs/dialogs.h> |  | ||||||
| #include <toolbox/path.h> |  | ||||||
| #include <flipper_application/flipper_application.h> |  | ||||||
| #include <loader/firmware_api/firmware_api.h> |  | ||||||
| 
 |  | ||||||
| #define TAG "FapLoader" |  | ||||||
| 
 |  | ||||||
| struct FapLoader { |  | ||||||
|     FlipperApplication* app; |  | ||||||
|     Storage* storage; |  | ||||||
|     DialogsApp* dialogs; |  | ||||||
|     Gui* gui; |  | ||||||
|     FuriString* fap_path; |  | ||||||
|     ViewDispatcher* view_dispatcher; |  | ||||||
|     Loading* loading; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| bool fap_loader_load_name_and_icon( |  | ||||||
|     FuriString* path, |  | ||||||
|     Storage* storage, |  | ||||||
|     uint8_t** icon_ptr, |  | ||||||
|     FuriString* item_name) { |  | ||||||
|     FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); |  | ||||||
| 
 |  | ||||||
|     FlipperApplicationPreloadStatus preload_res = |  | ||||||
|         flipper_application_preload_manifest(app, furi_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); |  | ||||||
|         } |  | ||||||
|         furi_string_set(item_name, manifest->name); |  | ||||||
|         load_success = true; |  | ||||||
|     } else { |  | ||||||
|         FURI_LOG_E(TAG, "FAP Loader failed to preload %s", furi_string_get_cstr(path)); |  | ||||||
|         load_success = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     flipper_application_free(app); |  | ||||||
|     return load_success; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool fap_loader_item_callback( |  | ||||||
|     FuriString* path, |  | ||||||
|     void* context, |  | ||||||
|     uint8_t** icon_ptr, |  | ||||||
|     FuriString* item_name) { |  | ||||||
|     FapLoader* fap_loader = context; |  | ||||||
|     furi_assert(fap_loader); |  | ||||||
|     return fap_loader_load_name_and_icon(path, fap_loader->storage, icon_ptr, item_name); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool fap_loader_run_selected_app(FapLoader* loader) { |  | ||||||
|     furi_assert(loader); |  | ||||||
| 
 |  | ||||||
|     FuriString* error_message; |  | ||||||
| 
 |  | ||||||
|     error_message = furi_string_alloc_set("unknown error"); |  | ||||||
| 
 |  | ||||||
|     bool file_selected = false; |  | ||||||
|     bool show_error = true; |  | ||||||
|     do { |  | ||||||
|         file_selected = true; |  | ||||||
|         loader->app = flipper_application_alloc(loader->storage, firmware_api_interface); |  | ||||||
|         size_t start = furi_get_tick(); |  | ||||||
| 
 |  | ||||||
|         FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path)); |  | ||||||
| 
 |  | ||||||
|         FlipperApplicationPreloadStatus preload_res = |  | ||||||
|             flipper_application_preload(loader->app, furi_string_get_cstr(loader->fap_path)); |  | ||||||
|         if(preload_res != FlipperApplicationPreloadStatusSuccess) { |  | ||||||
|             const char* err_msg = flipper_application_preload_status_to_string(preload_res); |  | ||||||
|             furi_string_printf(error_message, "Preload failed: %s", err_msg); |  | ||||||
|             FURI_LOG_E( |  | ||||||
|                 TAG, |  | ||||||
|                 "FAP Loader failed to preload %s: %s", |  | ||||||
|                 furi_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); |  | ||||||
|             furi_string_printf(error_message, "Load failed: %s", err_msg); |  | ||||||
|             FURI_LOG_E( |  | ||||||
|                 TAG, |  | ||||||
|                 "FAP Loader failed to map to memory %s: %s", |  | ||||||
|                 furi_string_get_cstr(loader->fap_path), |  | ||||||
|                 err_msg); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); |  | ||||||
|         FURI_LOG_I(TAG, "FAP Loader is starting app"); |  | ||||||
| 
 |  | ||||||
|         FuriThread* thread = flipper_application_spawn(loader->app, NULL); |  | ||||||
| 
 |  | ||||||
|         /* This flag is set by the debugger - to break on app start */ |  | ||||||
|         if(furi_hal_debug_is_gdb_session_active()) { |  | ||||||
|             FURI_LOG_W(TAG, "Triggering BP for debugger"); |  | ||||||
|             /* After hitting this, you can set breakpoints in your .fap's code
 |  | ||||||
|              * Note that you have to toggle breakpoints that were set before */ |  | ||||||
|             __asm volatile("bkpt 0"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         FuriString* app_name = furi_string_alloc(); |  | ||||||
|         path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name); |  | ||||||
|         furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); |  | ||||||
|         furi_string_free(app_name); |  | ||||||
| 
 |  | ||||||
|         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); |  | ||||||
| 
 |  | ||||||
|         FuriString* buffer; |  | ||||||
|         buffer = furi_string_alloc(); |  | ||||||
|         furi_string_printf(buffer, "%s", furi_string_get_cstr(error_message)); |  | ||||||
|         furi_string_replace(buffer, ":", "\n"); |  | ||||||
|         dialog_message_set_text( |  | ||||||
|             message, furi_string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter); |  | ||||||
| 
 |  | ||||||
|         dialog_message_show(loader->dialogs, message); |  | ||||||
|         dialog_message_free(message); |  | ||||||
|         furi_string_free(buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furi_string_free(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_unknown_10px, |  | ||||||
|         .hide_ext = true, |  | ||||||
|         .item_loader_callback = fap_loader_item_callback, |  | ||||||
|         .item_loader_context = loader, |  | ||||||
|         .base_path = EXT_PATH("apps"), |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     return dialog_file_browser_show( |  | ||||||
|         loader->dialogs, loader->fap_path, loader->fap_path, &browser_options); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static FapLoader* fap_loader_alloc(const char* path) { |  | ||||||
|     FapLoader* loader = malloc(sizeof(FapLoader)); //-V799
 |  | ||||||
|     loader->fap_path = furi_string_alloc_set(path); |  | ||||||
|     loader->storage = furi_record_open(RECORD_STORAGE); |  | ||||||
|     loader->dialogs = furi_record_open(RECORD_DIALOGS); |  | ||||||
|     loader->gui = furi_record_open(RECORD_GUI); |  | ||||||
|     loader->view_dispatcher = view_dispatcher_alloc(); |  | ||||||
|     loader->loading = loading_alloc(); |  | ||||||
|     view_dispatcher_attach_to_gui( |  | ||||||
|         loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen); |  | ||||||
|     view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading)); |  | ||||||
|     return loader; |  | ||||||
| } //-V773
 |  | ||||||
| 
 |  | ||||||
| static void fap_loader_free(FapLoader* loader) { |  | ||||||
|     view_dispatcher_remove_view(loader->view_dispatcher, 0); |  | ||||||
|     loading_free(loader->loading); |  | ||||||
|     view_dispatcher_free(loader->view_dispatcher); |  | ||||||
|     furi_string_free(loader->fap_path); |  | ||||||
|     furi_record_close(RECORD_GUI); |  | ||||||
|     furi_record_close(RECORD_DIALOGS); |  | ||||||
|     furi_record_close(RECORD_STORAGE); |  | ||||||
|     free(loader); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int32_t fap_loader_app(void* p) { |  | ||||||
|     FapLoader* loader; |  | ||||||
|     if(p) { |  | ||||||
|         loader = fap_loader_alloc((const char*)p); |  | ||||||
|         view_dispatcher_switch_to_view(loader->view_dispatcher, 0); |  | ||||||
|         fap_loader_run_selected_app(loader); |  | ||||||
|     } else { |  | ||||||
|         loader = fap_loader_alloc(EXT_PATH("apps")); |  | ||||||
|         while(fap_loader_select_app(loader)) { |  | ||||||
|             view_dispatcher_switch_to_view(loader->view_dispatcher, 0); |  | ||||||
|             fap_loader_run_selected_app(loader); |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fap_loader_free(loader); |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include <storage/storage.h> |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| typedef struct FapLoader FapLoader; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Load name and icon from FAP file. |  | ||||||
|  *  |  | ||||||
|  * @param path Path to FAP file. |  | ||||||
|  * @param storage Storage instance. |  | ||||||
|  * @param icon_ptr Icon pointer. |  | ||||||
|  * @param item_name Application name. |  | ||||||
|  * @return true if icon and name were loaded successfully. |  | ||||||
|  */ |  | ||||||
| bool fap_loader_load_name_and_icon( |  | ||||||
|     FuriString* path, |  | ||||||
|     Storage* storage, |  | ||||||
|     uint8_t** icon_ptr, |  | ||||||
|     FuriString* item_name); |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| @ -4,9 +4,9 @@ | |||||||
| #include <gui/icon.h> | #include <gui/icon.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     FlipperApplicationFlagDefault = 0, |     FlipperInternalApplicationFlagDefault = 0, | ||||||
|     FlipperApplicationFlagInsomniaSafe = (1 << 0), |     FlipperInternalApplicationFlagInsomniaSafe = (1 << 0), | ||||||
| } FlipperApplicationFlag; | } FlipperInternalApplicationFlag; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     const FuriThreadCallback app; |     const FuriThreadCallback app; | ||||||
| @ -14,48 +14,41 @@ typedef struct { | |||||||
|     const char* appid; |     const char* appid; | ||||||
|     const size_t stack_size; |     const size_t stack_size; | ||||||
|     const Icon* icon; |     const Icon* icon; | ||||||
|     const FlipperApplicationFlag flags; |     const FlipperInternalApplicationFlag flags; | ||||||
| } FlipperApplication; | } FlipperInternalApplication; | ||||||
| 
 | 
 | ||||||
| typedef void (*FlipperOnStartHook)(void); | typedef void (*FlipperInternalOnStartHook)(void); | ||||||
| 
 | 
 | ||||||
| extern const char* FLIPPER_AUTORUN_APP_NAME; | extern const char* FLIPPER_AUTORUN_APP_NAME; | ||||||
| 
 | 
 | ||||||
| /* Services list
 | /* Services list
 | ||||||
|  * Spawned on startup |  * Spawned on startup | ||||||
|  */ |  */ | ||||||
| extern const FlipperApplication FLIPPER_SERVICES[]; | extern const FlipperInternalApplication FLIPPER_SERVICES[]; | ||||||
| extern const size_t FLIPPER_SERVICES_COUNT; | extern const size_t FLIPPER_SERVICES_COUNT; | ||||||
| 
 | 
 | ||||||
| /* Apps list
 | /* Apps list
 | ||||||
|  * Spawned by loader |  * Spawned by loader | ||||||
|  */ |  */ | ||||||
| extern const FlipperApplication FLIPPER_APPS[]; | extern const FlipperInternalApplication FLIPPER_APPS[]; | ||||||
| extern const size_t FLIPPER_APPS_COUNT; | extern const size_t FLIPPER_APPS_COUNT; | ||||||
| 
 | 
 | ||||||
| /* On system start hooks
 | /* On system start hooks
 | ||||||
|  * Called by loader, after OS initialization complete |  * Called by loader, after OS initialization complete | ||||||
|  */ |  */ | ||||||
| extern const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[]; | extern const FlipperInternalOnStartHook FLIPPER_ON_SYSTEM_START[]; | ||||||
| extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; | extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; | ||||||
| 
 | 
 | ||||||
| /* System apps
 | /* System apps
 | ||||||
|  * Can only be spawned by loader by name |  * Can only be spawned by loader by name | ||||||
|  */ |  */ | ||||||
| extern const FlipperApplication FLIPPER_SYSTEM_APPS[]; | extern const FlipperInternalApplication FLIPPER_SYSTEM_APPS[]; | ||||||
| extern const size_t FLIPPER_SYSTEM_APPS_COUNT; | extern const size_t FLIPPER_SYSTEM_APPS_COUNT; | ||||||
| 
 | 
 | ||||||
| /* Separate scene app holder
 | extern const FlipperInternalApplication FLIPPER_ARCHIVE; | ||||||
|  * Spawned by loader |  | ||||||
|  */ |  | ||||||
| extern const FlipperApplication FLIPPER_SCENE; |  | ||||||
| extern const FlipperApplication FLIPPER_SCENE_APPS[]; |  | ||||||
| extern const size_t FLIPPER_SCENE_APPS_COUNT; |  | ||||||
| 
 |  | ||||||
| extern const FlipperApplication FLIPPER_ARCHIVE; |  | ||||||
| 
 | 
 | ||||||
| /* Settings list
 | /* Settings list
 | ||||||
|  * Spawned by loader |  * Spawned by loader | ||||||
|  */ |  */ | ||||||
| extern const FlipperApplication FLIPPER_SETTINGS_APPS[]; | extern const FlipperInternalApplication FLIPPER_SETTINGS_APPS[]; | ||||||
| extern const size_t FLIPPER_SETTINGS_APPS_COUNT; | extern const size_t FLIPPER_SETTINGS_APPS_COUNT; | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ static void desktop_loader_callback(const void* message, void* context) { | |||||||
|         view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); |         view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
| static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { | static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { | ||||||
|     UNUSED(context); |     UNUSED(context); | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|  | |||||||
| @ -16,8 +16,6 @@ | |||||||
| #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") | #define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") | ||||||
| #define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") | #define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") | ||||||
| 
 | 
 | ||||||
| #define FAP_LOADER_APP_NAME "Applications" |  | ||||||
| 
 |  | ||||||
| static void desktop_scene_main_new_idle_animation_callback(void* context) { | static void desktop_scene_main_new_idle_animation_callback(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     Desktop* desktop = context; |     Desktop* desktop = context; | ||||||
| @ -40,7 +38,8 @@ static void desktop_scene_main_interact_animation_callback(void* context) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #ifdef APP_ARCHIVE | #ifdef APP_ARCHIVE | ||||||
| static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { | static void | ||||||
|  |     desktop_switch_to_app(Desktop* desktop, const FlipperInternalApplication* flipper_app) { | ||||||
|     furi_assert(desktop); |     furi_assert(desktop); | ||||||
|     furi_assert(flipper_app); |     furi_assert(flipper_app); | ||||||
|     furi_assert(flipper_app->app); |     furi_assert(flipper_app->app); | ||||||
| @ -67,30 +66,16 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) { | static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) { | ||||||
|     do { |     if(loader_start_with_gui_error(desktop->loader, path, NULL) != LoaderStatusOk) { | ||||||
|         LoaderStatus status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, path); |         loader_start(desktop->loader, "Passport", NULL, NULL); | ||||||
|         if(status == LoaderStatusOk) break; |     } | ||||||
|         FURI_LOG_E(TAG, "loader_start failed: %d", status); |  | ||||||
| 
 |  | ||||||
|         status = loader_start(desktop->loader, "Passport", NULL); |  | ||||||
|         if(status != LoaderStatusOk) { |  | ||||||
|             FURI_LOG_E(TAG, "loader_start failed: %d", status); |  | ||||||
|         } |  | ||||||
|     } while(false); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { | static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { | ||||||
|     LoaderStatus status = LoaderStatusErrorInternal; |     if(strlen(application->name_or_path) > 0) { | ||||||
|     if(application->is_external) { |         loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); | ||||||
|         status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, application->name_or_path); |  | ||||||
|     } else if(strlen(application->name_or_path) > 0) { |  | ||||||
|         status = loader_start(desktop->loader, application->name_or_path, NULL); |  | ||||||
|     } else { |     } else { | ||||||
|         status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, NULL); |         loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(status != LoaderStatusOk) { |  | ||||||
|         FURI_LOG_E(TAG, "loader_start failed: %d", status); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -148,10 +133,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenPowerOff: { |         case DesktopMainEventOpenPowerOff: { | ||||||
|             LoaderStatus status = loader_start(desktop->loader, "Power", "off"); |             loader_start(desktop->loader, "Power", "off", NULL); | ||||||
|             if(status != LoaderStatusOk) { |  | ||||||
|                 FURI_LOG_E(TAG, "loader_start failed: %d", status); |  | ||||||
|             } |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| @ -176,18 +158,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
|         case DesktopAnimationEventInteractAnimation: |         case DesktopAnimationEventInteractAnimation: | ||||||
|             if(!animation_manager_interact_process(desktop->animation_manager)) { |             if(!animation_manager_interact_process(desktop->animation_manager)) { | ||||||
|                 LoaderStatus status = loader_start(desktop->loader, "Passport", NULL); |                 loader_start(desktop->loader, "Passport", NULL, NULL); | ||||||
|                 if(status != LoaderStatusOk) { |  | ||||||
|                     FURI_LOG_E(TAG, "loader_start failed: %d", status); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         case DesktopMainEventOpenPassport: { |         case DesktopMainEventOpenPassport: { | ||||||
|             LoaderStatus status = loader_start(desktop->loader, "Passport", NULL); |             loader_start(desktop->loader, "Passport", NULL, NULL); | ||||||
|             if(status != LoaderStatusOk) { |  | ||||||
|                 FURI_LOG_E(TAG, "loader_start failed: %d", status); |  | ||||||
|             } |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         case DesktopMainEventOpenGame: { |         case DesktopMainEventOpenGame: { | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "dialogs.h" | #include "dialogs.h" | ||||||
| #include "dialogs_message.h" | #include "dialogs_message.h" | ||||||
| #include "view_holder.h" | #include <gui/view_holder.h> | ||||||
| #include <gui/modules/file_browser.h> | #include <gui/modules/file_browser.h> | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
|  | |||||||
| @ -1,20 +1,27 @@ | |||||||
| #include "loader.h" | #include "loader.h" | ||||||
| #include "loader_i.h" | #include "loader_i.h" | ||||||
| #include "loader_menu.h" |  | ||||||
| #include <applications.h> | #include <applications.h> | ||||||
|  | #include <storage/storage.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
|  | #include <flipper_application/flipper_application.h> | ||||||
|  | #include <loader/firmware_api/firmware_api.h> | ||||||
|  | 
 | ||||||
| #define TAG "Loader" | #define TAG "Loader" | ||||||
| #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF | #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF | ||||||
| // api
 | // api
 | ||||||
| 
 | 
 | ||||||
| LoaderStatus loader_start(Loader* loader, const char* name, const char* args) { | LoaderStatus | ||||||
|  |     loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { | ||||||
|     LoaderMessage message; |     LoaderMessage message; | ||||||
|     LoaderMessageLoaderStatusResult result; |     LoaderMessageLoaderStatusResult result; | ||||||
| 
 | 
 | ||||||
|     message.type = LoaderMessageTypeStartByName; |     message.type = LoaderMessageTypeStartByName; | ||||||
|     message.start.name = name; |     message.start.name = name; | ||||||
|     message.start.args = args; |     message.start.args = args; | ||||||
|  |     message.start.error_message = error_message; | ||||||
|     message.api_lock = api_lock_alloc_locked(); |     message.api_lock = api_lock_alloc_locked(); | ||||||
|     message.status_value = &result; |     message.status_value = &result; | ||||||
|     furi_message_queue_put(loader->queue, &message, FuriWaitForever); |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
| @ -22,6 +29,31 @@ LoaderStatus loader_start(Loader* loader, const char* name, const char* args) { | |||||||
|     return result.value; |     return result.value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { | ||||||
|  |     FuriString* error_message = furi_string_alloc(); | ||||||
|  |     LoaderStatus status = loader_start(loader, name, args, error_message); | ||||||
|  | 
 | ||||||
|  |     // TODO: we have many places where we can emit a double start, ex: desktop, menu
 | ||||||
|  |     // so i prefer to not show LoaderStatusErrorAppStarted error message for now
 | ||||||
|  |     if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { | ||||||
|  |         DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|  |         DialogMessage* message = dialog_message_alloc(); | ||||||
|  |         dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); | ||||||
|  |         dialog_message_set_buttons(message, NULL, NULL, NULL); | ||||||
|  | 
 | ||||||
|  |         furi_string_replace(error_message, ":", "\n"); | ||||||
|  |         dialog_message_set_text( | ||||||
|  |             message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter); | ||||||
|  | 
 | ||||||
|  |         dialog_message_show(dialogs, message); | ||||||
|  |         dialog_message_free(message); | ||||||
|  |         furi_record_close(RECORD_DIALOGS); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_string_free(error_message); | ||||||
|  |     return status; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool loader_lock(Loader* loader) { | bool loader_lock(Loader* loader) { | ||||||
|     LoaderMessage message; |     LoaderMessage message; | ||||||
|     LoaderMessageBoolResult result; |     LoaderMessageBoolResult result; | ||||||
| @ -73,27 +105,26 @@ static void loader_menu_closed_callback(void* context) { | |||||||
|     furi_message_queue_put(loader->queue, &message, FuriWaitForever); |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_menu_click_callback(const char* name, void* context) { | static void loader_applications_closed_callback(void* context) { | ||||||
|     Loader* loader = context; |     Loader* loader = context; | ||||||
|     loader_start(loader, name, NULL); |     LoaderMessage message; | ||||||
|  |     message.type = LoaderMessageTypeApplicationsClosed; | ||||||
|  |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { | static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     Loader* loader = context; |     Loader* loader = context; | ||||||
|     LoaderEvent event; |  | ||||||
| 
 | 
 | ||||||
|     if(thread_state == FuriThreadStateRunning) { |     if(thread_state == FuriThreadStateRunning) { | ||||||
|  |         LoaderEvent event; | ||||||
|         event.type = LoaderEventTypeApplicationStarted; |         event.type = LoaderEventTypeApplicationStarted; | ||||||
|         furi_pubsub_publish(loader->pubsub, &event); |         furi_pubsub_publish(loader->pubsub, &event); | ||||||
|     } else if(thread_state == FuriThreadStateStopped) { |     } else if(thread_state == FuriThreadStateStopped) { | ||||||
|         LoaderMessage message; |         LoaderMessage message; | ||||||
|         message.type = LoaderMessageTypeAppClosed; |         message.type = LoaderMessageTypeAppClosed; | ||||||
|         furi_message_queue_put(loader->queue, &message, FuriWaitForever); |         furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
| 
 |  | ||||||
|         event.type = LoaderEventTypeApplicationStopped; |  | ||||||
|         furi_pubsub_publish(loader->pubsub, &event); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -104,16 +135,17 @@ static Loader* loader_alloc() { | |||||||
|     loader->pubsub = furi_pubsub_alloc(); |     loader->pubsub = furi_pubsub_alloc(); | ||||||
|     loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); |     loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); | ||||||
|     loader->loader_menu = NULL; |     loader->loader_menu = NULL; | ||||||
|  |     loader->loader_applications = NULL; | ||||||
|     loader->app.args = NULL; |     loader->app.args = NULL; | ||||||
|     loader->app.name = NULL; |  | ||||||
|     loader->app.thread = NULL; |     loader->app.thread = NULL; | ||||||
|     loader->app.insomniac = false; |     loader->app.insomniac = false; | ||||||
|  |     loader->app.fap = NULL; | ||||||
|     return loader; |     return loader; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static FlipperApplication const* loader_find_application_by_name_in_list( | static FlipperInternalApplication const* loader_find_application_by_name_in_list( | ||||||
|     const char* name, |     const char* name, | ||||||
|     const FlipperApplication* list, |     const FlipperInternalApplication* list, | ||||||
|     const uint32_t n_apps) { |     const uint32_t n_apps) { | ||||||
|     for(size_t i = 0; i < n_apps; i++) { |     for(size_t i = 0; i < n_apps; i++) { | ||||||
|         if(strcmp(name, list[i].name) == 0) { |         if(strcmp(name, list[i].name) == 0) { | ||||||
| @ -123,8 +155,8 @@ static FlipperApplication const* loader_find_application_by_name_in_list( | |||||||
|     return NULL; |     return NULL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static const FlipperApplication* loader_find_application_by_name(const char* name) { | static const FlipperInternalApplication* loader_find_application_by_name(const char* name) { | ||||||
|     const FlipperApplication* application = NULL; |     const FlipperInternalApplication* application = NULL; | ||||||
|     application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); |     application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); | ||||||
|     if(!application) { |     if(!application) { | ||||||
|         application = loader_find_application_by_name_in_list( |         application = loader_find_application_by_name_in_list( | ||||||
| @ -138,25 +170,7 @@ static const FlipperApplication* loader_find_application_by_name(const char* nam | |||||||
|     return application; |     return application; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) { | ||||||
|     loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) { |  | ||||||
|     FURI_LOG_I(TAG, "Starting %s", app->name); |  | ||||||
| 
 |  | ||||||
|     // store args
 |  | ||||||
|     furi_assert(loader->app.args == NULL); |  | ||||||
|     if(args && strlen(args) > 0) { |  | ||||||
|         loader->app.args = strdup(args); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // store name
 |  | ||||||
|     furi_assert(loader->app.name == NULL); |  | ||||||
|     loader->app.name = strdup(app->name); |  | ||||||
| 
 |  | ||||||
|     // setup app thread
 |  | ||||||
|     loader->app.thread = |  | ||||||
|         furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args); |  | ||||||
|     furi_thread_set_appid(loader->app.thread, app->appid); |  | ||||||
| 
 |  | ||||||
|     // setup heap trace
 |     // setup heap trace
 | ||||||
|     FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); |     FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); | ||||||
|     if(mode > FuriHalRtcHeapTrackModeNone) { |     if(mode > FuriHalRtcHeapTrackModeNone) { | ||||||
| @ -166,14 +180,14 @@ static void | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // setup insomnia
 |     // setup insomnia
 | ||||||
|     if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) { |     if(!(flags & FlipperInternalApplicationFlagInsomniaSafe)) { | ||||||
|         furi_hal_power_insomnia_enter(); |         furi_hal_power_insomnia_enter(); | ||||||
|         loader->app.insomniac = true; |         loader->app.insomniac = true; | ||||||
|     } else { |     } else { | ||||||
|         loader->app.insomniac = false; |         loader->app.insomniac = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // setup app thread callbacks
 |     // setup thread state callbacks
 | ||||||
|     furi_thread_set_state_context(loader->app.thread, loader); |     furi_thread_set_state_context(loader->app.thread, loader); | ||||||
|     furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback); |     furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback); | ||||||
| 
 | 
 | ||||||
| @ -181,42 +195,206 @@ static void | |||||||
|     furi_thread_start(loader->app.thread); |     furi_thread_start(loader->app.thread); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void loader_start_internal_app( | ||||||
|  |     Loader* loader, | ||||||
|  |     const FlipperInternalApplication* app, | ||||||
|  |     const char* args) { | ||||||
|  |     FURI_LOG_I(TAG, "Starting %s", app->name); | ||||||
|  | 
 | ||||||
|  |     // store args
 | ||||||
|  |     furi_assert(loader->app.args == NULL); | ||||||
|  |     if(args && strlen(args) > 0) { | ||||||
|  |         loader->app.args = strdup(args); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     loader->app.thread = | ||||||
|  |         furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args); | ||||||
|  |     furi_thread_set_appid(loader->app.thread, app->appid); | ||||||
|  | 
 | ||||||
|  |     loader_start_app_thread(loader, app->flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_log_status_error( | ||||||
|  |     LoaderStatus status, | ||||||
|  |     FuriString* error_message, | ||||||
|  |     const char* format, | ||||||
|  |     va_list args) { | ||||||
|  |     if(error_message) { | ||||||
|  |         furi_string_vprintf(error_message, format, args); | ||||||
|  |         FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(error_message)); | ||||||
|  |     } else { | ||||||
|  |         FuriString* tmp = furi_string_alloc(); | ||||||
|  |         FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(tmp)); | ||||||
|  |         furi_string_free(tmp); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static LoaderStatus loader_make_status_error( | ||||||
|  |     LoaderStatus status, | ||||||
|  |     FuriString* error_message, | ||||||
|  |     const char* format, | ||||||
|  |     ...) { | ||||||
|  |     va_list args; | ||||||
|  |     va_start(args, format); | ||||||
|  |     loader_log_status_error(status, error_message, format, args); | ||||||
|  |     va_end(args); | ||||||
|  |     return status; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static LoaderStatus loader_make_success_status(FuriString* error_message) { | ||||||
|  |     if(error_message) { | ||||||
|  |         furi_string_set(error_message, "App started"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return LoaderStatusOk; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static LoaderStatus loader_start_external_app( | ||||||
|  |     Loader* loader, | ||||||
|  |     Storage* storage, | ||||||
|  |     const char* path, | ||||||
|  |     const char* args, | ||||||
|  |     FuriString* error_message) { | ||||||
|  |     LoaderStatus status = loader_make_success_status(error_message); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); | ||||||
|  |         size_t start = furi_get_tick(); | ||||||
|  | 
 | ||||||
|  |         FURI_LOG_I(TAG, "Loading %s", path); | ||||||
|  | 
 | ||||||
|  |         FlipperApplicationPreloadStatus preload_res = | ||||||
|  |             flipper_application_preload(loader->app.fap, path); | ||||||
|  |         if(preload_res != FlipperApplicationPreloadStatusSuccess) { | ||||||
|  |             const char* err_msg = flipper_application_preload_status_to_string(preload_res); | ||||||
|  |             status = loader_make_status_error( | ||||||
|  |                 LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         FURI_LOG_I(TAG, "Mapping"); | ||||||
|  |         FlipperApplicationLoadStatus load_status = | ||||||
|  |             flipper_application_map_to_memory(loader->app.fap); | ||||||
|  |         if(load_status != FlipperApplicationLoadStatusSuccess) { | ||||||
|  |             const char* err_msg = flipper_application_load_status_to_string(load_status); | ||||||
|  |             status = loader_make_status_error( | ||||||
|  |                 LoaderStatusErrorInternal, error_message, "Load failed %s: %s", path, err_msg); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); | ||||||
|  |         FURI_LOG_I(TAG, "Starting app"); | ||||||
|  | 
 | ||||||
|  |         loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); | ||||||
|  |         FuriString* app_name = furi_string_alloc(); | ||||||
|  |         path_extract_filename_no_ext(path, app_name); | ||||||
|  |         furi_thread_set_appid(loader->app.thread, furi_string_get_cstr(app_name)); | ||||||
|  |         furi_string_free(app_name); | ||||||
|  | 
 | ||||||
|  |         /* This flag is set by the debugger - to break on app start */ | ||||||
|  |         if(furi_hal_debug_is_gdb_session_active()) { | ||||||
|  |             FURI_LOG_W(TAG, "Triggering BP for debugger"); | ||||||
|  |             /* After hitting this, you can set breakpoints in your .fap's code
 | ||||||
|  |              * Note that you have to toggle breakpoints that were set before */ | ||||||
|  |             __asm volatile("bkpt 0"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         loader_start_app_thread(loader, FlipperInternalApplicationFlagDefault); | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     if(status != LoaderStatusOk) { | ||||||
|  |         flipper_application_free(loader->app.fap); | ||||||
|  |         loader->app.fap = NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return status; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // process messages
 | // process messages
 | ||||||
| 
 | 
 | ||||||
| static void loader_do_menu_show(Loader* loader) { | static void loader_do_menu_show(Loader* loader) { | ||||||
|     if(!loader->loader_menu) { |     if(!loader->loader_menu) { | ||||||
|         loader->loader_menu = loader_menu_alloc(); |         loader->loader_menu = loader_menu_alloc(loader_menu_closed_callback, loader); | ||||||
|         loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader); |  | ||||||
|         loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader); |  | ||||||
|         loader_menu_start(loader->loader_menu); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_do_menu_closed(Loader* loader) { | static void loader_do_menu_closed(Loader* loader) { | ||||||
|     if(loader->loader_menu) { |     if(loader->loader_menu) { | ||||||
|         loader_menu_stop(loader->loader_menu); |  | ||||||
|         loader_menu_free(loader->loader_menu); |         loader_menu_free(loader->loader_menu); | ||||||
|         loader->loader_menu = NULL; |         loader->loader_menu = NULL; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void loader_do_applications_show(Loader* loader) { | ||||||
|  |     if(!loader->loader_applications) { | ||||||
|  |         loader->loader_applications = | ||||||
|  |             loader_applications_alloc(loader_applications_closed_callback, loader); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_do_applications_closed(Loader* loader) { | ||||||
|  |     if(loader->loader_applications) { | ||||||
|  |         loader_applications_free(loader->loader_applications); | ||||||
|  |         loader->loader_applications = NULL; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static bool loader_do_is_locked(Loader* loader) { | static bool loader_do_is_locked(Loader* loader) { | ||||||
|     return loader->app.thread != NULL; |     return loader->app.thread != NULL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) { | static LoaderStatus loader_do_start_by_name( | ||||||
|     if(loader_do_is_locked(loader)) { |     Loader* loader, | ||||||
|         return LoaderStatusErrorAppStarted; |     const char* name, | ||||||
|     } |     const char* args, | ||||||
|  |     FuriString* error_message) { | ||||||
|  |     LoaderStatus status; | ||||||
|  |     do { | ||||||
|  |         // check lock
 | ||||||
|  |         if(loader_do_is_locked(loader)) { | ||||||
|  |             const char* current_thread_name = | ||||||
|  |                 furi_thread_get_name(furi_thread_get_id(loader->app.thread)); | ||||||
|  |             status = loader_make_status_error( | ||||||
|  |                 LoaderStatusErrorAppStarted, | ||||||
|  |                 error_message, | ||||||
|  |                 "Loader is locked, please close the \"%s\" first", | ||||||
|  |                 current_thread_name); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|     const FlipperApplication* app = loader_find_application_by_name(name); |         // check internal apps
 | ||||||
|  |         { | ||||||
|  |             const FlipperInternalApplication* app = loader_find_application_by_name(name); | ||||||
|  |             if(app) { | ||||||
|  |                 loader_start_internal_app(loader, app, args); | ||||||
|  |                 status = loader_make_success_status(error_message); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|     if(!app) { |         // check Applications
 | ||||||
|         return LoaderStatusErrorUnknownApp; |         if(strcmp(name, LOADER_APPLICATIONS_NAME) == 0) { | ||||||
|     } |             loader_do_applications_show(loader); | ||||||
|  |             status = loader_make_success_status(error_message); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|     loader_start_internal_app(loader, app, args); |         // check external apps
 | ||||||
|     return LoaderStatusOk; |         { | ||||||
|  |             Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|  |             if(storage_file_exists(storage, name)) { | ||||||
|  |                 status = loader_start_external_app(loader, storage, name, args, error_message); | ||||||
|  |                 furi_record_close(RECORD_STORAGE); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             furi_record_close(RECORD_STORAGE); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         status = loader_make_status_error( | ||||||
|  |             LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name); | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return status; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool loader_do_lock(Loader* loader) { | static bool loader_do_lock(Loader* loader) { | ||||||
| @ -229,13 +407,16 @@ static bool loader_do_lock(Loader* loader) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_do_unlock(Loader* loader) { | static void loader_do_unlock(Loader* loader) { | ||||||
|     furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); |     furi_check(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); | ||||||
|     loader->app.thread = NULL; |     loader->app.thread = NULL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_do_app_closed(Loader* loader) { | static void loader_do_app_closed(Loader* loader) { | ||||||
|     furi_assert(loader->app.thread); |     furi_assert(loader->app.thread); | ||||||
|     FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); | 
 | ||||||
|  |     furi_thread_join(loader->app.thread); | ||||||
|  |     FURI_LOG_I(TAG, "App returned: %li", furi_thread_get_return_code(loader->app.thread)); | ||||||
|  | 
 | ||||||
|     if(loader->app.args) { |     if(loader->app.args) { | ||||||
|         free(loader->app.args); |         free(loader->app.args); | ||||||
|         loader->app.args = NULL; |         loader->app.args = NULL; | ||||||
| @ -245,12 +426,20 @@ static void loader_do_app_closed(Loader* loader) { | |||||||
|         furi_hal_power_insomnia_exit(); |         furi_hal_power_insomnia_exit(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     free(loader->app.name); |     if(loader->app.fap) { | ||||||
|     loader->app.name = NULL; |         flipper_application_free(loader->app.fap); | ||||||
|  |         loader->app.fap = NULL; | ||||||
|  |         loader->app.thread = NULL; | ||||||
|  |     } else { | ||||||
|  |         furi_thread_free(loader->app.thread); | ||||||
|  |         loader->app.thread = NULL; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     furi_thread_join(loader->app.thread); |     FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); | ||||||
|     furi_thread_free(loader->app.thread); | 
 | ||||||
|     loader->app.thread = NULL; |     LoaderEvent event; | ||||||
|  |     event.type = LoaderEventTypeApplicationStopped; | ||||||
|  |     furi_pubsub_publish(loader->pubsub, &event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // app
 | // app
 | ||||||
| @ -266,7 +455,7 @@ int32_t loader_srv(void* p) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) { |     if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) { | ||||||
|         loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL); |         loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL, NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     LoaderMessage message; |     LoaderMessage message; | ||||||
| @ -274,8 +463,8 @@ int32_t loader_srv(void* p) { | |||||||
|         if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { |         if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { | ||||||
|             switch(message.type) { |             switch(message.type) { | ||||||
|             case LoaderMessageTypeStartByName: |             case LoaderMessageTypeStartByName: | ||||||
|                 message.status_value->value = |                 message.status_value->value = loader_do_start_by_name( | ||||||
|                     loader_do_start_by_name(loader, message.start.name, message.start.args); |                     loader, message.start.name, message.start.args, message.start.error_message); | ||||||
|                 api_lock_unlock(message.api_lock); |                 api_lock_unlock(message.api_lock); | ||||||
|                 break; |                 break; | ||||||
|             case LoaderMessageTypeShowMenu: |             case LoaderMessageTypeShowMenu: | ||||||
| @ -297,6 +486,10 @@ int32_t loader_srv(void* p) { | |||||||
|                 break; |                 break; | ||||||
|             case LoaderMessageTypeUnlock: |             case LoaderMessageTypeUnlock: | ||||||
|                 loader_do_unlock(loader); |                 loader_do_unlock(loader); | ||||||
|  |                 break; | ||||||
|  |             case LoaderMessageTypeApplicationsClosed: | ||||||
|  |                 loader_do_applications_closed(loader); | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ extern "C" { | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #define RECORD_LOADER "loader" | #define RECORD_LOADER "loader" | ||||||
|  | #define LOADER_APPLICATIONS_NAME "Applications" | ||||||
| 
 | 
 | ||||||
| typedef struct Loader Loader; | typedef struct Loader Loader; | ||||||
| 
 | 
 | ||||||
| @ -25,28 +26,57 @@ typedef struct { | |||||||
|     LoaderEventType type; |     LoaderEventType type; | ||||||
| } LoaderEvent; | } LoaderEvent; | ||||||
| 
 | 
 | ||||||
| /** Start application
 | /**
 | ||||||
|  * @param name - application name |  * @brief Start application | ||||||
|  * @param args - application arguments |  * @param[in] instance loader instance | ||||||
|  * @retval true on success |  * @param[in] name application name | ||||||
|  |  * @param[in] args application arguments | ||||||
|  |  * @param[out] error_message detailed error message, can be NULL | ||||||
|  |  * @return LoaderStatus  | ||||||
|  */ |  */ | ||||||
| LoaderStatus loader_start(Loader* instance, const char* name, const char* args); | LoaderStatus | ||||||
|  |     loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); | ||||||
| 
 | 
 | ||||||
| /** Lock application start
 | /**
 | ||||||
|  * @retval true on success |  * @brief Start application with GUI error message | ||||||
|  |  * @param[in] instance loader instance | ||||||
|  |  * @param[in] name application name | ||||||
|  |  * @param[in] args application arguments | ||||||
|  |  * @return LoaderStatus  | ||||||
|  |  */ | ||||||
|  | LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); | ||||||
|  | 
 | ||||||
|  | /** 
 | ||||||
|  |  * @brief Lock application start | ||||||
|  |  * @param[in] instance loader instance | ||||||
|  |  * @return true on success | ||||||
|  */ |  */ | ||||||
| bool loader_lock(Loader* instance); | bool loader_lock(Loader* instance); | ||||||
| 
 | 
 | ||||||
| /** Unlock application start */ | /**
 | ||||||
|  |  * @brief Unlock application start | ||||||
|  |  * @param[in] instance loader instance | ||||||
|  |  */ | ||||||
| void loader_unlock(Loader* instance); | void loader_unlock(Loader* instance); | ||||||
| 
 | 
 | ||||||
| /** Get loader lock status */ | /**
 | ||||||
|  |  * @brief Check if loader is locked | ||||||
|  |  * @param[in] instance loader instance | ||||||
|  |  * @return true if locked | ||||||
|  |  */ | ||||||
| bool loader_is_locked(Loader* instance); | bool loader_is_locked(Loader* instance); | ||||||
| 
 | 
 | ||||||
| /** Show primary loader */ | /**
 | ||||||
|  |  * @brief Show loader menu | ||||||
|  |  * @param[in] instance loader instance | ||||||
|  |  */ | ||||||
| void loader_show_menu(Loader* instance); | void loader_show_menu(Loader* instance); | ||||||
| 
 | 
 | ||||||
| /** Show primary loader */ | /**
 | ||||||
|  |  * @brief Get loader pubsub | ||||||
|  |  * @param[in] instance loader instance | ||||||
|  |  * @return FuriPubSub*  | ||||||
|  |  */ | ||||||
| FuriPubSub* loader_get_pubsub(Loader* instance); | FuriPubSub* loader_get_pubsub(Loader* instance); | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
|  | |||||||
							
								
								
									
										146
									
								
								applications/services/loader/loader_applications.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								applications/services/loader/loader_applications.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | |||||||
|  | #include "loader.h" | ||||||
|  | #include "loader_applications.h" | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
|  | #include <flipper_application/flipper_application.h> | ||||||
|  | #include <assets_icons.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_holder.h> | ||||||
|  | #include <gui/modules/loading.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "LoaderApplications" | ||||||
|  | 
 | ||||||
|  | struct LoaderApplications { | ||||||
|  |     FuriThread* thread; | ||||||
|  |     void (*closed_cb)(void*); | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int32_t loader_applications_thread(void* p); | ||||||
|  | 
 | ||||||
|  | LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context) { | ||||||
|  |     LoaderApplications* loader_applications = malloc(sizeof(LoaderApplications)); | ||||||
|  |     loader_applications->thread = | ||||||
|  |         furi_thread_alloc_ex(TAG, 512, loader_applications_thread, (void*)loader_applications); | ||||||
|  |     loader_applications->closed_cb = closed_cb; | ||||||
|  |     loader_applications->context = context; | ||||||
|  |     furi_thread_start(loader_applications->thread); | ||||||
|  |     return loader_applications; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void loader_applications_free(LoaderApplications* loader_applications) { | ||||||
|  |     furi_assert(loader_applications); | ||||||
|  |     furi_thread_join(loader_applications->thread); | ||||||
|  |     furi_thread_free(loader_applications->thread); | ||||||
|  |     free(loader_applications); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     FuriString* fap_path; | ||||||
|  |     DialogsApp* dialogs; | ||||||
|  |     Storage* storage; | ||||||
|  | } LoaderApplicationsApp; | ||||||
|  | 
 | ||||||
|  | static LoaderApplicationsApp* loader_applications_app_alloc() { | ||||||
|  |     LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799
 | ||||||
|  |     app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); | ||||||
|  |     app->dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|  |     app->storage = furi_record_open(RECORD_STORAGE); | ||||||
|  |     return app; | ||||||
|  | } //-V773
 | ||||||
|  | 
 | ||||||
|  | static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) { | ||||||
|  |     furi_assert(loader_applications_app); | ||||||
|  |     furi_record_close(RECORD_DIALOGS); | ||||||
|  |     furi_record_close(RECORD_STORAGE); | ||||||
|  |     furi_string_free(loader_applications_app->fap_path); | ||||||
|  |     free(loader_applications_app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool loader_applications_item_callback( | ||||||
|  |     FuriString* path, | ||||||
|  |     void* context, | ||||||
|  |     uint8_t** icon_ptr, | ||||||
|  |     FuriString* item_name) { | ||||||
|  |     LoaderApplicationsApp* loader_applications_app = context; | ||||||
|  |     furi_assert(loader_applications_app); | ||||||
|  |     return flipper_application_load_name_and_icon( | ||||||
|  |         path, loader_applications_app->storage, icon_ptr, item_name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) { | ||||||
|  |     const DialogsFileBrowserOptions browser_options = { | ||||||
|  |         .extension = ".fap", | ||||||
|  |         .skip_assets = true, | ||||||
|  |         .icon = &I_unknown_10px, | ||||||
|  |         .hide_ext = true, | ||||||
|  |         .item_loader_callback = loader_applications_item_callback, | ||||||
|  |         .item_loader_context = loader_applications_app, | ||||||
|  |         .base_path = EXT_PATH("apps"), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return dialog_file_browser_show( | ||||||
|  |         loader_applications_app->dialogs, | ||||||
|  |         loader_applications_app->fap_path, | ||||||
|  |         loader_applications_app->fap_path, | ||||||
|  |         &browser_options); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #define APPLICATION_STOP_EVENT 1 | ||||||
|  | 
 | ||||||
|  | static void loader_pubsub_callback(const void* message, void* context) { | ||||||
|  |     const LoaderEvent* event = message; | ||||||
|  |     const FuriThreadId thread_id = (FuriThreadId)context; | ||||||
|  | 
 | ||||||
|  |     if(event->type == LoaderEventTypeApplicationStopped) { | ||||||
|  |         furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_applications_start_app(const char* name) { | ||||||
|  |     // start loading animation
 | ||||||
|  |     Gui* gui = furi_record_open(RECORD_GUI); | ||||||
|  |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|  |     Loading* loading = loading_alloc(); | ||||||
|  | 
 | ||||||
|  |     view_holder_attach_to_gui(view_holder, gui); | ||||||
|  |     view_holder_set_view(view_holder, loading_get_view(loading)); | ||||||
|  |     view_holder_start(view_holder); | ||||||
|  | 
 | ||||||
|  |     // load app
 | ||||||
|  |     FuriThreadId thread_id = furi_thread_get_current_id(); | ||||||
|  |     Loader* loader = furi_record_open(RECORD_LOADER); | ||||||
|  |     FuriPubSubSubscription* subscription = | ||||||
|  |         furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id); | ||||||
|  | 
 | ||||||
|  |     LoaderStatus status = loader_start_with_gui_error(loader, name, NULL); | ||||||
|  | 
 | ||||||
|  |     if(status == LoaderStatusOk) { | ||||||
|  |         furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription); | ||||||
|  |     furi_record_close(RECORD_LOADER); | ||||||
|  | 
 | ||||||
|  |     // stop loading animation
 | ||||||
|  |     view_holder_stop(view_holder); | ||||||
|  |     view_holder_free(view_holder); | ||||||
|  |     loading_free(loading); | ||||||
|  |     furi_record_close(RECORD_GUI); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int32_t loader_applications_thread(void* p) { | ||||||
|  |     LoaderApplications* loader_applications = p; | ||||||
|  |     LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc(); | ||||||
|  | 
 | ||||||
|  |     while(loader_applications_select_app(loader_applications_app)) { | ||||||
|  |         loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     loader_applications_app_free(loader_applications_app); | ||||||
|  | 
 | ||||||
|  |     if(loader_applications->closed_cb) { | ||||||
|  |         loader_applications->closed_cb(loader_applications->context); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								applications/services/loader/loader_applications.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								applications/services/loader/loader_applications.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct LoaderApplications LoaderApplications; | ||||||
|  | 
 | ||||||
|  | LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context); | ||||||
|  | 
 | ||||||
|  | void loader_applications_free(LoaderApplications* loader_applications); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -50,21 +50,11 @@ static void loader_cli_open(FuriString* args, Loader* loader) { | |||||||
| 
 | 
 | ||||||
|         const char* app_name_str = furi_string_get_cstr(app_name); |         const char* app_name_str = furi_string_get_cstr(app_name); | ||||||
| 
 | 
 | ||||||
|         LoaderStatus status = loader_start(loader, app_name_str, args_str); |         FuriString* error_message = furi_string_alloc(); | ||||||
| 
 |         if(loader_start(loader, app_name_str, args_str, error_message) != LoaderStatusOk) { | ||||||
|         switch(status) { |             printf("%s\r\n", furi_string_get_cstr(error_message)); | ||||||
|         case LoaderStatusOk: |  | ||||||
|             break; |  | ||||||
|         case LoaderStatusErrorAppStarted: |  | ||||||
|             printf("Can't start, application is running"); |  | ||||||
|             break; |  | ||||||
|         case LoaderStatusErrorUnknownApp: |  | ||||||
|             printf("%s doesn't exists\r\n", app_name_str); |  | ||||||
|             break; |  | ||||||
|         case LoaderStatusErrorInternal: |  | ||||||
|             printf("Internal error\r\n"); |  | ||||||
|             break; |  | ||||||
|         } |         } | ||||||
|  |         furi_string_free(error_message); | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     furi_string_free(app_name); |     furi_string_free(app_name); | ||||||
|  | |||||||
| @ -1,20 +1,23 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <toolbox/api_lock.h> | #include <toolbox/api_lock.h> | ||||||
|  | #include <flipper_application/flipper_application.h> | ||||||
| #include "loader.h" | #include "loader.h" | ||||||
| #include "loader_menu.h" | #include "loader_menu.h" | ||||||
|  | #include "loader_applications.h" | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     char* args; |     char* args; | ||||||
|     char* name; |  | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     bool insomniac; |     bool insomniac; | ||||||
|  |     FlipperApplication* fap; | ||||||
| } LoaderAppData; | } LoaderAppData; | ||||||
| 
 | 
 | ||||||
| struct Loader { | struct Loader { | ||||||
|     FuriPubSub* pubsub; |     FuriPubSub* pubsub; | ||||||
|     FuriMessageQueue* queue; |     FuriMessageQueue* queue; | ||||||
|     LoaderMenu* loader_menu; |     LoaderMenu* loader_menu; | ||||||
|  |     LoaderApplications* loader_applications; | ||||||
|     LoaderAppData app; |     LoaderAppData app; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -23,6 +26,7 @@ typedef enum { | |||||||
|     LoaderMessageTypeAppClosed, |     LoaderMessageTypeAppClosed, | ||||||
|     LoaderMessageTypeShowMenu, |     LoaderMessageTypeShowMenu, | ||||||
|     LoaderMessageTypeMenuClosed, |     LoaderMessageTypeMenuClosed, | ||||||
|  |     LoaderMessageTypeApplicationsClosed, | ||||||
|     LoaderMessageTypeLock, |     LoaderMessageTypeLock, | ||||||
|     LoaderMessageTypeUnlock, |     LoaderMessageTypeUnlock, | ||||||
|     LoaderMessageTypeIsLocked, |     LoaderMessageTypeIsLocked, | ||||||
| @ -31,6 +35,7 @@ typedef enum { | |||||||
| typedef struct { | typedef struct { | ||||||
|     const char* name; |     const char* name; | ||||||
|     const char* args; |     const char* args; | ||||||
|  |     FuriString* error_message; | ||||||
| } LoaderMessageStartByName; | } LoaderMessageStartByName; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  | |||||||
| @ -5,106 +5,76 @@ | |||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| #include <applications.h> | #include <applications.h> | ||||||
| 
 | 
 | ||||||
|  | #include "loader.h" | ||||||
| #include "loader_menu.h" | #include "loader_menu.h" | ||||||
| 
 | 
 | ||||||
| #define TAG "LoaderMenu" | #define TAG "LoaderMenu" | ||||||
| 
 | 
 | ||||||
| struct LoaderMenu { | struct LoaderMenu { | ||||||
|     Gui* gui; |  | ||||||
|     ViewDispatcher* view_dispatcher; |  | ||||||
|     Menu* primary_menu; |  | ||||||
|     Submenu* settings_menu; |  | ||||||
| 
 |  | ||||||
|     void (*closed_callback)(void*); |  | ||||||
|     void* closed_callback_context; |  | ||||||
| 
 |  | ||||||
|     void (*click_callback)(const char*, void*); |  | ||||||
|     void* click_callback_context; |  | ||||||
| 
 |  | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|  |     void (*closed_cb)(void*); | ||||||
|  |     void* context; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | static int32_t loader_menu_thread(void* p); | ||||||
|  | 
 | ||||||
|  | LoaderMenu* loader_menu_alloc(void (*closed_cb)(void*), void* context) { | ||||||
|  |     LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu)); | ||||||
|  |     loader_menu->closed_cb = closed_cb; | ||||||
|  |     loader_menu->context = context; | ||||||
|  |     loader_menu->thread = furi_thread_alloc_ex(TAG, 1024, loader_menu_thread, loader_menu); | ||||||
|  |     furi_thread_start(loader_menu->thread); | ||||||
|  |     return loader_menu; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void loader_menu_free(LoaderMenu* loader_menu) { | ||||||
|  |     furi_assert(loader_menu); | ||||||
|  |     furi_thread_join(loader_menu->thread); | ||||||
|  |     furi_thread_free(loader_menu->thread); | ||||||
|  |     free(loader_menu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     LoaderMenuViewPrimary, |     LoaderMenuViewPrimary, | ||||||
|     LoaderMenuViewSettings, |     LoaderMenuViewSettings, | ||||||
| } LoaderMenuView; | } LoaderMenuView; | ||||||
| 
 | 
 | ||||||
| static int32_t loader_menu_thread(void* p); | typedef struct { | ||||||
|  |     Gui* gui; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     Menu* primary_menu; | ||||||
|  |     Submenu* settings_menu; | ||||||
|  | } LoaderMenuApp; | ||||||
| 
 | 
 | ||||||
| LoaderMenu* loader_menu_alloc() { | static void loader_menu_start(const char* name) { | ||||||
|     LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu)); |     Loader* loader = furi_record_open(RECORD_LOADER); | ||||||
|     loader_menu->gui = furi_record_open(RECORD_GUI); |     loader_start_with_gui_error(loader, name, NULL); | ||||||
|     loader_menu->view_dispatcher = view_dispatcher_alloc(); |     furi_record_close(RECORD_LOADER); | ||||||
|     loader_menu->primary_menu = menu_alloc(); |  | ||||||
|     loader_menu->settings_menu = submenu_alloc(); |  | ||||||
|     loader_menu->thread = NULL; |  | ||||||
|     return loader_menu; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_menu_free(LoaderMenu* loader_menu) { |  | ||||||
|     furi_assert(loader_menu); |  | ||||||
|     // check if thread is running
 |  | ||||||
|     furi_assert(!loader_menu->thread); |  | ||||||
| 
 |  | ||||||
|     submenu_free(loader_menu->settings_menu); |  | ||||||
|     menu_free(loader_menu->primary_menu); |  | ||||||
|     view_dispatcher_free(loader_menu->view_dispatcher); |  | ||||||
|     furi_record_close(RECORD_GUI); |  | ||||||
|     free(loader_menu); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_menu_start(LoaderMenu* loader_menu) { |  | ||||||
|     furi_assert(loader_menu); |  | ||||||
|     furi_assert(!loader_menu->thread); |  | ||||||
|     loader_menu->thread = furi_thread_alloc_ex(TAG, 1024, loader_menu_thread, loader_menu); |  | ||||||
|     furi_thread_start(loader_menu->thread); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_menu_stop(LoaderMenu* loader_menu) { |  | ||||||
|     furi_assert(loader_menu); |  | ||||||
|     furi_assert(loader_menu->thread); |  | ||||||
|     view_dispatcher_stop(loader_menu->view_dispatcher); |  | ||||||
|     furi_thread_join(loader_menu->thread); |  | ||||||
|     furi_thread_free(loader_menu->thread); |  | ||||||
|     loader_menu->thread = NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_menu_set_closed_callback( |  | ||||||
|     LoaderMenu* loader_menu, |  | ||||||
|     void (*callback)(void*), |  | ||||||
|     void* context) { |  | ||||||
|     loader_menu->closed_callback = callback; |  | ||||||
|     loader_menu->closed_callback_context = context; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_menu_set_click_callback( |  | ||||||
|     LoaderMenu* loader_menu, |  | ||||||
|     void (*callback)(const char*, void*), |  | ||||||
|     void* context) { |  | ||||||
|     loader_menu->click_callback = callback; |  | ||||||
|     loader_menu->click_callback_context = context; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_menu_callback(void* context, uint32_t index) { | static void loader_menu_callback(void* context, uint32_t index) { | ||||||
|     LoaderMenu* loader_menu = context; |     UNUSED(context); | ||||||
|     const char* name = FLIPPER_APPS[index].name; |     const char* name = FLIPPER_APPS[index].name; | ||||||
|     if(loader_menu->click_callback) { |     loader_menu_start(name); | ||||||
|         loader_menu->click_callback(name, loader_menu->click_callback_context); | } | ||||||
|     } | 
 | ||||||
|  | static void loader_menu_applications_callback(void* context, uint32_t index) { | ||||||
|  |     UNUSED(index); | ||||||
|  |     UNUSED(context); | ||||||
|  |     const char* name = LOADER_APPLICATIONS_NAME; | ||||||
|  |     loader_menu_start(name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_menu_settings_menu_callback(void* context, uint32_t index) { | static void loader_menu_settings_menu_callback(void* context, uint32_t index) { | ||||||
|     LoaderMenu* loader_menu = context; |     UNUSED(context); | ||||||
|     const char* name = FLIPPER_SETTINGS_APPS[index].name; |     const char* name = FLIPPER_SETTINGS_APPS[index].name; | ||||||
|     if(loader_menu->click_callback) { |     loader_menu_start(name); | ||||||
|         loader_menu->click_callback(name, loader_menu->click_callback_context); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_menu_switch_to_settings(void* context, uint32_t index) { | static void loader_menu_switch_to_settings(void* context, uint32_t index) { | ||||||
|     UNUSED(index); |     UNUSED(index); | ||||||
|     LoaderMenu* loader_menu = context; |     LoaderMenuApp* app = context; | ||||||
|     view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewSettings); |     view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewSettings); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static uint32_t loader_menu_switch_to_primary(void* context) { | static uint32_t loader_menu_switch_to_primary(void* context) { | ||||||
| @ -117,30 +87,32 @@ static uint32_t loader_menu_exit(void* context) { | |||||||
|     return VIEW_NONE; |     return VIEW_NONE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_menu_build_menu(LoaderMenu* loader_menu) { | static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { | ||||||
|     size_t i; |     size_t i; | ||||||
|     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { |     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||||
|         menu_add_item( |         menu_add_item( | ||||||
|             loader_menu->primary_menu, |             app->primary_menu, | ||||||
|             FLIPPER_APPS[i].name, |             FLIPPER_APPS[i].name, | ||||||
|             FLIPPER_APPS[i].icon, |             FLIPPER_APPS[i].icon, | ||||||
|             i, |             i, | ||||||
|             loader_menu_callback, |             loader_menu_callback, | ||||||
|             (void*)loader_menu); |             (void*)menu); | ||||||
|     } |     } | ||||||
|     menu_add_item( |     menu_add_item( | ||||||
|         loader_menu->primary_menu, |         app->primary_menu, "Settings", &A_Settings_14, i++, loader_menu_switch_to_settings, app); | ||||||
|         "Settings", |     menu_add_item( | ||||||
|         &A_Settings_14, |         app->primary_menu, | ||||||
|  |         LOADER_APPLICATIONS_NAME, | ||||||
|  |         &A_Plugins_14, | ||||||
|         i++, |         i++, | ||||||
|         loader_menu_switch_to_settings, |         loader_menu_applications_callback, | ||||||
|         loader_menu); |         (void*)menu); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void loader_menu_build_submenu(LoaderMenu* loader_menu) { | static void loader_menu_build_submenu(LoaderMenuApp* app, LoaderMenu* loader_menu) { | ||||||
|     for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { |     for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             loader_menu->settings_menu, |             app->settings_menu, | ||||||
|             FLIPPER_SETTINGS_APPS[i].name, |             FLIPPER_SETTINGS_APPS[i].name, | ||||||
|             i, |             i, | ||||||
|             loader_menu_settings_menu_callback, |             loader_menu_settings_menu_callback, | ||||||
| @ -148,40 +120,59 @@ static void loader_menu_build_submenu(LoaderMenu* loader_menu) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static LoaderMenuApp* loader_menu_app_alloc(LoaderMenu* loader_menu) { | ||||||
|  |     LoaderMenuApp* app = malloc(sizeof(LoaderMenuApp)); | ||||||
|  |     app->gui = furi_record_open(RECORD_GUI); | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     app->primary_menu = menu_alloc(); | ||||||
|  |     app->settings_menu = submenu_alloc(); | ||||||
|  | 
 | ||||||
|  |     loader_menu_build_menu(app, loader_menu); | ||||||
|  |     loader_menu_build_submenu(app, loader_menu); | ||||||
|  | 
 | ||||||
|  |     // Primary menu
 | ||||||
|  |     View* primary_view = menu_get_view(app->primary_menu); | ||||||
|  |     view_set_context(primary_view, app->primary_menu); | ||||||
|  |     view_set_previous_callback(primary_view, loader_menu_exit); | ||||||
|  |     view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewPrimary, primary_view); | ||||||
|  | 
 | ||||||
|  |     // Settings menu
 | ||||||
|  |     View* settings_view = submenu_get_view(app->settings_menu); | ||||||
|  |     view_set_context(settings_view, app->settings_menu); | ||||||
|  |     view_set_previous_callback(settings_view, loader_menu_switch_to_primary); | ||||||
|  |     view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_menu_app_free(LoaderMenuApp* app) { | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, LoaderMenuViewPrimary); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, LoaderMenuViewSettings); | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     menu_free(app->primary_menu); | ||||||
|  |     submenu_free(app->settings_menu); | ||||||
|  |     furi_record_close(RECORD_GUI); | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int32_t loader_menu_thread(void* p) { | static int32_t loader_menu_thread(void* p) { | ||||||
|     LoaderMenu* loader_menu = p; |     LoaderMenu* loader_menu = p; | ||||||
|     furi_assert(loader_menu); |     furi_assert(loader_menu); | ||||||
| 
 | 
 | ||||||
|     loader_menu_build_menu(loader_menu); |     LoaderMenuApp* app = loader_menu_app_alloc(loader_menu); | ||||||
|     loader_menu_build_submenu(loader_menu); |  | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_attach_to_gui( |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|         loader_menu->view_dispatcher, loader_menu->gui, ViewDispatcherTypeFullscreen); |     view_dispatcher_run(app->view_dispatcher); | ||||||
| 
 | 
 | ||||||
|     // Primary menu
 |     if(loader_menu->closed_cb) { | ||||||
|     View* primary_view = menu_get_view(loader_menu->primary_menu); |         loader_menu->closed_cb(loader_menu->context); | ||||||
|     view_set_context(primary_view, loader_menu->primary_menu); |  | ||||||
|     view_set_previous_callback(primary_view, loader_menu_exit); |  | ||||||
|     view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary, primary_view); |  | ||||||
| 
 |  | ||||||
|     // Settings menu
 |  | ||||||
|     View* settings_view = submenu_get_view(loader_menu->settings_menu); |  | ||||||
|     view_set_context(settings_view, loader_menu->settings_menu); |  | ||||||
|     view_set_previous_callback(settings_view, loader_menu_switch_to_primary); |  | ||||||
|     view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewSettings, settings_view); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_enable_queue(loader_menu->view_dispatcher); |  | ||||||
|     view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary); |  | ||||||
| 
 |  | ||||||
|     // run view dispatcher
 |  | ||||||
|     view_dispatcher_run(loader_menu->view_dispatcher); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary); |  | ||||||
|     view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewSettings); |  | ||||||
| 
 |  | ||||||
|     if(loader_menu->closed_callback) { |  | ||||||
|         loader_menu->closed_callback(loader_menu->closed_callback_context); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     loader_menu_app_free(app); | ||||||
|  | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| @ -7,24 +7,10 @@ extern "C" { | |||||||
| 
 | 
 | ||||||
| typedef struct LoaderMenu LoaderMenu; | typedef struct LoaderMenu LoaderMenu; | ||||||
| 
 | 
 | ||||||
| LoaderMenu* loader_menu_alloc(); | LoaderMenu* loader_menu_alloc(void (*closed_cb)(void*), void* context); | ||||||
| 
 | 
 | ||||||
| void loader_menu_free(LoaderMenu* loader_menu); | void loader_menu_free(LoaderMenu* loader_menu); | ||||||
| 
 | 
 | ||||||
| void loader_menu_start(LoaderMenu* loader_menu); |  | ||||||
| 
 |  | ||||||
| void loader_menu_stop(LoaderMenu* loader_menu); |  | ||||||
| 
 |  | ||||||
| void loader_menu_set_closed_callback( |  | ||||||
|     LoaderMenu* loader_menu, |  | ||||||
|     void (*callback)(void*), |  | ||||||
|     void* context); |  | ||||||
| 
 |  | ||||||
| void loader_menu_set_click_callback( |  | ||||||
|     LoaderMenu* loader_menu, |  | ||||||
|     void (*callback)(const char*, void*), |  | ||||||
|     void* context); |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @ -52,7 +52,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) | |||||||
|             snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); |             snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); | ||||||
|             app_args = args_temp; |             app_args = args_temp; | ||||||
|         } |         } | ||||||
|         LoaderStatus status = loader_start(loader, app_name, app_args); |         LoaderStatus status = loader_start(loader, app_name, app_args, NULL); | ||||||
|         if(status == LoaderStatusErrorAppStarted) { |         if(status == LoaderStatusErrorAppStarted) { | ||||||
|             result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; |             result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; | ||||||
|         } else if(status == LoaderStatusErrorInternal) { |         } else if(status == LoaderStatusErrorInternal) { | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ | |||||||
| #include "desktop_settings_scene.h" | #include "desktop_settings_scene.h" | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <dialogs/dialogs.h> | #include <dialogs/dialogs.h> | ||||||
| #include <fap_loader/fap_loader_app.h> |  | ||||||
| 
 | 
 | ||||||
| #define EXTERNAL_APPLICATION_NAME ("[External Application]") | #define EXTERNAL_APPLICATION_NAME ("[External Application]") | ||||||
| #define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) | #define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 1) | ||||||
| @ -65,7 +64,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| #ifdef APP_FAP_LOADER |  | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, |         submenu, | ||||||
|         EXTERNAL_APPLICATION_NAME, |         EXTERNAL_APPLICATION_NAME, | ||||||
| @ -75,7 +73,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { | |||||||
|     if(curr_favorite_app->is_external) { |     if(curr_favorite_app->is_external) { | ||||||
|         pre_select_item = EXTERNAL_APPLICATION_INDEX; |         pre_select_item = EXTERNAL_APPLICATION_INDEX; | ||||||
|     } |     } | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
|     submenu_set_header( |     submenu_set_header( | ||||||
|         submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); |         submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); | ||||||
|  | |||||||
| @ -172,7 +172,7 @@ static void storage_move_to_sd_mount_callback(const void* message, void* context | |||||||
| 
 | 
 | ||||||
|     if(storage_event->type == StorageEventTypeCardMount) { |     if(storage_event->type == StorageEventTypeCardMount) { | ||||||
|         Loader* loader = furi_record_open(RECORD_LOADER); |         Loader* loader = furi_record_open(RECORD_LOADER); | ||||||
|         loader_start(loader, "StorageMoveToSd", NULL); |         loader_start(loader, "StorageMoveToSd", NULL, NULL); | ||||||
|         furi_record_close(RECORD_LOADER); |         furi_record_close(RECORD_LOADER); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ static void updater_start_app(void* context, uint32_t arg) { | |||||||
|      * So, accessing its record would cause a deadlock  |      * So, accessing its record would cause a deadlock  | ||||||
|      */ |      */ | ||||||
|     Loader* loader = furi_record_open(RECORD_LOADER); |     Loader* loader = furi_record_open(RECORD_LOADER); | ||||||
|     loader_start(loader, "UpdaterApp", NULL); |     loader_start(loader, "UpdaterApp", NULL, NULL); | ||||||
|     furi_record_close(RECORD_LOADER); |     furi_record_close(RECORD_LOADER); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,30.1,, | Version,+,31.0,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.h,, | Header,+,applications/services/cli/cli_vcp.h,, | ||||||
| @ -735,9 +735,11 @@ Function,+,filesystem_api_error_get_desc,const char*,FS_Error | |||||||
| Function,-,fiprintf,int,"FILE*, const char*, ..." | Function,-,fiprintf,int,"FILE*, const char*, ..." | ||||||
| Function,-,fiscanf,int,"FILE*, const char*, ..." | Function,-,fiscanf,int,"FILE*, const char*, ..." | ||||||
| Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" | Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" | ||||||
|  | Function,+,flipper_application_alloc_thread,FuriThread*,"FlipperApplication*, const char*" | ||||||
| Function,+,flipper_application_free,void,FlipperApplication* | Function,+,flipper_application_free,void,FlipperApplication* | ||||||
| Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* | Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* | ||||||
| Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* | Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* | ||||||
|  | Function,+,flipper_application_load_name_and_icon,_Bool,"FuriString*, Storage*, uint8_t**, FuriString*" | ||||||
| Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus | Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus | ||||||
| Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" | Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" | ||||||
| Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* | Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* | ||||||
| @ -747,7 +749,6 @@ Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescr | |||||||
| Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | ||||||
| Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | ||||||
| Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus | Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus | ||||||
| Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" |  | ||||||
| Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* | Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* | ||||||
| Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* | Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* | ||||||
| Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" | Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" | ||||||
| @ -1419,7 +1420,8 @@ Function,+,loader_get_pubsub,FuriPubSub*,Loader* | |||||||
| Function,+,loader_is_locked,_Bool,Loader* | Function,+,loader_is_locked,_Bool,Loader* | ||||||
| Function,+,loader_lock,_Bool,Loader* | Function,+,loader_lock,_Bool,Loader* | ||||||
| Function,+,loader_show_menu,void,Loader* | Function,+,loader_show_menu,void,Loader* | ||||||
| Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" | Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" | ||||||
|  | Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" | ||||||
| Function,+,loader_unlock,void,Loader* | Function,+,loader_unlock,void,Loader* | ||||||
| Function,+,loading_alloc,Loading*, | Function,+,loading_alloc,Loading*, | ||||||
| Function,+,loading_free,void,Loading* | Function,+,loading_free,void,Loading* | ||||||
|  | |||||||
| 
 | 
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,30.1,, | Version,+,31.0,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.h,, | Header,+,applications/services/cli/cli_vcp.h,, | ||||||
| @ -892,9 +892,11 @@ Function,-,finitel,int,long double | |||||||
| Function,-,fiprintf,int,"FILE*, const char*, ..." | Function,-,fiprintf,int,"FILE*, const char*, ..." | ||||||
| Function,-,fiscanf,int,"FILE*, const char*, ..." | Function,-,fiscanf,int,"FILE*, const char*, ..." | ||||||
| Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" | Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*" | ||||||
|  | Function,+,flipper_application_alloc_thread,FuriThread*,"FlipperApplication*, const char*" | ||||||
| Function,+,flipper_application_free,void,FlipperApplication* | Function,+,flipper_application_free,void,FlipperApplication* | ||||||
| Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* | Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication* | ||||||
| Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* | Function,+,flipper_application_is_plugin,_Bool,FlipperApplication* | ||||||
|  | Function,+,flipper_application_load_name_and_icon,_Bool,"FuriString*, Storage*, uint8_t**, FuriString*" | ||||||
| Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus | Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus | ||||||
| Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" | Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*" | ||||||
| Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* | Function,+,flipper_application_manifest_is_target_compatible,_Bool,const FlipperApplicationManifest* | ||||||
| @ -904,7 +906,6 @@ Function,+,flipper_application_plugin_get_descriptor,const FlipperAppPluginDescr | |||||||
| Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | ||||||
| Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*" | ||||||
| Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus | Function,+,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus | ||||||
| Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*" |  | ||||||
| Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* | Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage* | ||||||
| Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* | Function,+,flipper_format_buffered_file_close,_Bool,FlipperFormat* | ||||||
| Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" | Function,+,flipper_format_buffered_file_open_always,_Bool,"FlipperFormat*, const char*" | ||||||
| @ -1825,7 +1826,8 @@ Function,+,loader_get_pubsub,FuriPubSub*,Loader* | |||||||
| Function,+,loader_is_locked,_Bool,Loader* | Function,+,loader_is_locked,_Bool,Loader* | ||||||
| Function,+,loader_lock,_Bool,Loader* | Function,+,loader_lock,_Bool,Loader* | ||||||
| Function,+,loader_show_menu,void,Loader* | Function,+,loader_show_menu,void,Loader* | ||||||
| Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*" | Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" | ||||||
|  | Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" | ||||||
| Function,+,loader_unlock,void,Loader* | Function,+,loader_unlock,void,Loader* | ||||||
| Function,+,loading_alloc,Loading*, | Function,+,loading_alloc,Loading*, | ||||||
| Function,+,loading_free,void,Loading* | Function,+,loading_free,void,Loading* | ||||||
|  | |||||||
| 
 | 
| @ -2,6 +2,7 @@ | |||||||
| #include "elf/elf_file.h" | #include "elf/elf_file.h" | ||||||
| #include <notification/notification_messages.h> | #include <notification/notification_messages.h> | ||||||
| #include "application_assets.h" | #include "application_assets.h" | ||||||
|  | #include <loader/firmware_api/firmware_api.h> | ||||||
| 
 | 
 | ||||||
| #include <m-list.h> | #include <m-list.h> | ||||||
| 
 | 
 | ||||||
| @ -81,6 +82,12 @@ void flipper_application_free(FlipperApplication* app) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     elf_file_free(app->elf); |     elf_file_free(app->elf); | ||||||
|  | 
 | ||||||
|  |     if(app->ep_thread_args) { | ||||||
|  |         free(app->ep_thread_args); | ||||||
|  |         app->ep_thread_args = NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -224,10 +231,19 @@ static int32_t flipper_application_thread(void* context) { | |||||||
|     return ret_code; |     return ret_code; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) { | FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args) { | ||||||
|     furi_check(app->thread == NULL); |     furi_check(app->thread == NULL); | ||||||
|     furi_check(!flipper_application_is_plugin(app)); |     furi_check(!flipper_application_is_plugin(app)); | ||||||
|     app->ep_thread_args = args; | 
 | ||||||
|  |     if(app->ep_thread_args) { | ||||||
|  |         free(app->ep_thread_args); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(args) { | ||||||
|  |         app->ep_thread_args = strdup(args); | ||||||
|  |     } else { | ||||||
|  |         app->ep_thread_args = NULL; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); |     const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app); | ||||||
|     app->thread = furi_thread_alloc_ex( |     app->thread = furi_thread_alloc_ex( | ||||||
| @ -289,4 +305,32 @@ const FlipperAppPluginDescriptor* | |||||||
|         lib_descriptor->ep_api_version); |         lib_descriptor->ep_api_version); | ||||||
| 
 | 
 | ||||||
|     return lib_descriptor; |     return lib_descriptor; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool flipper_application_load_name_and_icon( | ||||||
|  |     FuriString* path, | ||||||
|  |     Storage* storage, | ||||||
|  |     uint8_t** icon_ptr, | ||||||
|  |     FuriString* item_name) { | ||||||
|  |     FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); | ||||||
|  | 
 | ||||||
|  |     FlipperApplicationPreloadStatus preload_res = | ||||||
|  |         flipper_application_preload_manifest(app, furi_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); | ||||||
|  |         } | ||||||
|  |         furi_string_set(item_name, manifest->name); | ||||||
|  |         load_success = true; | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "Failed to preload %s", furi_string_get_cstr(path)); | ||||||
|  |         load_success = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     flipper_application_free(app); | ||||||
|  |     return load_success; | ||||||
| } | } | ||||||
| @ -106,14 +106,14 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic | |||||||
| FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); | FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * @brief Create application thread at entry point address, using app name and |  * @brief Allocate application thread at entry point address, using app name and | ||||||
|  * stack size from metadata. Returned thread isn't started yet.  |  * stack size from metadata. Returned thread isn't started yet.  | ||||||
|  * Can be only called once for application instance. |  * Can be only called once for application instance. | ||||||
|  * @param app Applicaiton pointer |  * @param app Applicaiton pointer | ||||||
|  * @param args Object to pass to app's entry point |  * @param args Args to pass to app's entry point | ||||||
|  * @return Created thread |  * @return Created thread | ||||||
|  */ |  */ | ||||||
| FuriThread* flipper_application_spawn(FlipperApplication* app, void* args); | FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * @brief Check if application is a plugin (not a runnable standalone app) |  * @brief Check if application is a plugin (not a runnable standalone app) | ||||||
| @ -149,6 +149,21 @@ typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)( | |||||||
| const FlipperAppPluginDescriptor* | const FlipperAppPluginDescriptor* | ||||||
|     flipper_application_plugin_get_descriptor(FlipperApplication* app); |     flipper_application_plugin_get_descriptor(FlipperApplication* app); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Load name and icon from FAP file. | ||||||
|  |  *  | ||||||
|  |  * @param path Path to FAP file. | ||||||
|  |  * @param storage Storage instance. | ||||||
|  |  * @param icon_ptr Icon pointer. | ||||||
|  |  * @param item_name Application name. | ||||||
|  |  * @return true if icon and name were loaded successfully. | ||||||
|  |  */ | ||||||
|  | bool flipper_application_load_name_and_icon( | ||||||
|  |     FuriString* path, | ||||||
|  |     Storage* storage, | ||||||
|  |     uint8_t** icon_ptr, | ||||||
|  |     FuriString* item_name); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -52,9 +52,7 @@ class Main(App): | |||||||
|                 if not self.args.launch_app: |                 if not self.args.launch_app: | ||||||
|                     return 0 |                     return 0 | ||||||
| 
 | 
 | ||||||
|                 storage.send_and_wait_eol( |                 storage.send_and_wait_eol(f"loader open {fap_dst_path}\r") | ||||||
|                     f'loader open "Applications" {fap_dst_path}\r' |  | ||||||
|                 ) |  | ||||||
| 
 | 
 | ||||||
|                 if len(result := storage.read.until(storage.CLI_EOL)): |                 if len(result := storage.read.until(storage.CLI_EOL)): | ||||||
|                     self.logger.error(f"Unexpected response: {result.decode('ascii')}") |                     self.logger.error(f"Unexpected response: {result.decode('ascii')}") | ||||||
|  | |||||||
| @ -353,12 +353,18 @@ class AppBuildset: | |||||||
| 
 | 
 | ||||||
| class ApplicationsCGenerator: | class ApplicationsCGenerator: | ||||||
|     APP_TYPE_MAP = { |     APP_TYPE_MAP = { | ||||||
|         FlipperAppType.SERVICE: ("FlipperApplication", "FLIPPER_SERVICES"), |         FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"), | ||||||
|         FlipperAppType.SYSTEM: ("FlipperApplication", "FLIPPER_SYSTEM_APPS"), |         FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"), | ||||||
|         FlipperAppType.APP: ("FlipperApplication", "FLIPPER_APPS"), |         FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"), | ||||||
|         FlipperAppType.DEBUG: ("FlipperApplication", "FLIPPER_DEBUG_APPS"), |         FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"), | ||||||
|         FlipperAppType.SETTINGS: ("FlipperApplication", "FLIPPER_SETTINGS_APPS"), |         FlipperAppType.SETTINGS: ( | ||||||
|         FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"), |             "FlipperInternalApplication", | ||||||
|  |             "FLIPPER_SETTINGS_APPS", | ||||||
|  |         ), | ||||||
|  |         FlipperAppType.STARTUP: ( | ||||||
|  |             "FlipperInternalOnStartHook", | ||||||
|  |             "FLIPPER_ON_SYSTEM_START", | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     def __init__(self, buildset: AppBuildset, autorun_app: str = ""): |     def __init__(self, buildset: AppBuildset, autorun_app: str = ""): | ||||||
| @ -379,7 +385,7 @@ class ApplicationsCGenerator: | |||||||
|      .appid = "{app.appid}",  |      .appid = "{app.appid}",  | ||||||
|      .stack_size = {app.stack_size}, |      .stack_size = {app.stack_size}, | ||||||
|      .icon = {f"&{app.icon}" if app.icon else "NULL"}, |      .icon = {f"&{app.icon}" if app.icon else "NULL"}, | ||||||
|      .flags = {'|'.join(f"FlipperApplicationFlag{flag}" for flag in app.flags)} }}""" |      .flags = {'|'.join(f"FlipperInternalApplicationFlag{flag}" for flag in app.flags)} }}""" | ||||||
| 
 | 
 | ||||||
|     def generate(self): |     def generate(self): | ||||||
|         contents = [ |         contents = [ | ||||||
| @ -408,7 +414,7 @@ class ApplicationsCGenerator: | |||||||
|             contents.extend( |             contents.extend( | ||||||
|                 [ |                 [ | ||||||
|                     self.get_app_ep_forward(archive_app[0]), |                     self.get_app_ep_forward(archive_app[0]), | ||||||
|                     f"const FlipperApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", |                     f"const FlipperInternalApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};", | ||||||
|                 ] |                 ] | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ class Main(App): | |||||||
|                     storage_ops.recursive_send(fap_dst_path, fap_local_path, False) |                     storage_ops.recursive_send(fap_dst_path, fap_local_path, False) | ||||||
| 
 | 
 | ||||||
|                 fap_host_app = self.args.targets[0] |                 fap_host_app = self.args.targets[0] | ||||||
|                 startup_command = f'"Applications" {fap_host_app}' |                 startup_command = f"{fap_host_app}" | ||||||
|                 if self.args.host_app: |                 if self.args.host_app: | ||||||
|                     startup_command = self.args.host_app |                     startup_command = self.args.host_app | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sergey Gavrilov
						Sergey Gavrilov