[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:
Sergey Gavrilov 2023-06-23 15:01:40 +03:00 committed by GitHub
parent 4ddfe05a59
commit 761a14e6e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 723 additions and 581 deletions

View File

@ -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

View File

@ -12,7 +12,6 @@ App(
"subghz", "subghz",
"bad_usb", "bad_usb",
"u2f", "u2f",
"fap_loader",
"archive", "archive",
], ],
) )

View File

@ -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);

View File

@ -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);

View File

@ -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,
)

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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: {

View File

@ -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

View File

@ -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;
} }
} }
} }

View File

@ -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

View 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;
}

View 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

View File

@ -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);

View File

@ -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 {

View File

@ -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;
} }

View File

@ -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

View File

@ -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) {

View File

@ -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:");

View File

@ -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);
} }
} }

View File

@ -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);
} }

View File

@ -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 entry status name type params
2 Version + 30.1 31.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
735 Function - fiprintf int FILE*, const char*, ...
736 Function - fiscanf int FILE*, const char*, ...
737 Function + flipper_application_alloc FlipperApplication* Storage*, const ElfApiInterface*
738 Function + flipper_application_alloc_thread FuriThread* FlipperApplication*, const char*
739 Function + flipper_application_free void FlipperApplication*
740 Function + flipper_application_get_manifest const FlipperApplicationManifest* FlipperApplication*
741 Function + flipper_application_is_plugin _Bool FlipperApplication*
742 Function + flipper_application_load_name_and_icon _Bool FuriString*, Storage*, uint8_t**, FuriString*
743 Function + flipper_application_load_status_to_string const char* FlipperApplicationLoadStatus
744 Function + flipper_application_manifest_is_compatible _Bool const FlipperApplicationManifest*, const ElfApiInterface*
745 Function + flipper_application_manifest_is_target_compatible _Bool const FlipperApplicationManifest*
749 Function + flipper_application_preload FlipperApplicationPreloadStatus FlipperApplication*, const char*
750 Function + flipper_application_preload_manifest FlipperApplicationPreloadStatus FlipperApplication*, const char*
751 Function + flipper_application_preload_status_to_string const char* FlipperApplicationPreloadStatus
Function + flipper_application_spawn FuriThread* FlipperApplication*, void*
752 Function + flipper_format_buffered_file_alloc FlipperFormat* Storage*
753 Function + flipper_format_buffered_file_close _Bool FlipperFormat*
754 Function + flipper_format_buffered_file_open_always _Bool FlipperFormat*, const char*
1420 Function + loader_is_locked _Bool Loader*
1421 Function + loader_lock _Bool Loader*
1422 Function + loader_show_menu void Loader*
1423 Function + loader_start LoaderStatus Loader*, const char*, const char* Loader*, const char*, const char*, FuriString*
1424 Function + loader_start_with_gui_error LoaderStatus Loader*, const char*, const char*
1425 Function + loader_unlock void Loader*
1426 Function + loading_alloc Loading*
1427 Function + loading_free void Loading*

View File

@ -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*

1 entry status name type params
2 Version + 30.1 31.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
892 Function - fiprintf int FILE*, const char*, ...
893 Function - fiscanf int FILE*, const char*, ...
894 Function + flipper_application_alloc FlipperApplication* Storage*, const ElfApiInterface*
895 Function + flipper_application_alloc_thread FuriThread* FlipperApplication*, const char*
896 Function + flipper_application_free void FlipperApplication*
897 Function + flipper_application_get_manifest const FlipperApplicationManifest* FlipperApplication*
898 Function + flipper_application_is_plugin _Bool FlipperApplication*
899 Function + flipper_application_load_name_and_icon _Bool FuriString*, Storage*, uint8_t**, FuriString*
900 Function + flipper_application_load_status_to_string const char* FlipperApplicationLoadStatus
901 Function + flipper_application_manifest_is_compatible _Bool const FlipperApplicationManifest*, const ElfApiInterface*
902 Function + flipper_application_manifest_is_target_compatible _Bool const FlipperApplicationManifest*
906 Function + flipper_application_preload FlipperApplicationPreloadStatus FlipperApplication*, const char*
907 Function + flipper_application_preload_manifest FlipperApplicationPreloadStatus FlipperApplication*, const char*
908 Function + flipper_application_preload_status_to_string const char* FlipperApplicationPreloadStatus
Function + flipper_application_spawn FuriThread* FlipperApplication*, void*
909 Function + flipper_format_buffered_file_alloc FlipperFormat* Storage*
910 Function + flipper_format_buffered_file_close _Bool FlipperFormat*
911 Function + flipper_format_buffered_file_open_always _Bool FlipperFormat*, const char*
1826 Function + loader_is_locked _Bool Loader*
1827 Function + loader_lock _Bool Loader*
1828 Function + loader_show_menu void Loader*
1829 Function + loader_start LoaderStatus Loader*, const char*, const char* Loader*, const char*, const char*, FuriString*
1830 Function + loader_start_with_gui_error LoaderStatus Loader*, const char*, const char*
1831 Function + loader_unlock void Loader*
1832 Function + loading_alloc Loading*
1833 Function + loading_free void Loading*

View File

@ -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(
@ -290,3 +306,31 @@ const FlipperAppPluginDescriptor*
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;
}

View File

@ -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

View File

@ -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')}")

View File

@ -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])};",
] ]
) )

View File

@ -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