[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
- `bad_usb` - Bad USB application
- `fap_loader` - External applications loader
- `gpio` - GPIO application: includes USART bridge and GPIO control
- `ibutton` - iButton application, onewire keys and more
- `infrared` - Infrared application, controls your IR devices

View file

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

View file

@ -6,7 +6,7 @@
#include <core/common_defines.h>
#include <core/log.h>
#include <gui/modules/file_browser_worker.h>
#include <fap_loader/fap_loader_app.h>
#include <flipper_application/flipper_application.h>
#include <math.h>
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) {
Storage* storage = furi_record_open(RECORD_STORAGE);
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;
}
furi_record_close(RECORD_STORAGE);

View file

@ -11,17 +11,28 @@
#define SCENE_STATE_DEFAULT (0)
#define SCENE_STATE_NEED_REFRESH (1)
static const char* flipper_app_name[] = {
[ArchiveFileTypeIButton] = "iButton",
[ArchiveFileTypeNFC] = "NFC",
[ArchiveFileTypeSubGhz] = "Sub-GHz",
[ArchiveFileTypeLFRFID] = "125 kHz RFID",
[ArchiveFileTypeInfrared] = "Infrared",
[ArchiveFileTypeBadUsb] = "Bad USB",
[ArchiveFileTypeU2f] = "U2F",
[ArchiveFileTypeUpdateManifest] = "UpdaterApp",
[ArchiveFileTypeApplication] = "Applications",
};
const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
switch(file_type) {
case ArchiveFileTypeIButton:
return "iButton";
case ArchiveFileTypeNFC:
return "NFC";
case ArchiveFileTypeSubGhz:
return "Sub-GHz";
case ArchiveFileTypeLFRFID:
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) {
furi_assert(message);
@ -39,20 +50,20 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
UNUSED(browser);
Loader* loader = furi_record_open(RECORD_LOADER);
LoaderStatus status;
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));
}
const char* app_name = archive_get_flipper_app_name(selected->type);
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
if(app_name) {
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);

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>
typedef enum {
FlipperApplicationFlagDefault = 0,
FlipperApplicationFlagInsomniaSafe = (1 << 0),
} FlipperApplicationFlag;
FlipperInternalApplicationFlagDefault = 0,
FlipperInternalApplicationFlagInsomniaSafe = (1 << 0),
} FlipperInternalApplicationFlag;
typedef struct {
const FuriThreadCallback app;
@ -14,48 +14,41 @@ typedef struct {
const char* appid;
const size_t stack_size;
const Icon* icon;
const FlipperApplicationFlag flags;
} FlipperApplication;
const FlipperInternalApplicationFlag flags;
} FlipperInternalApplication;
typedef void (*FlipperOnStartHook)(void);
typedef void (*FlipperInternalOnStartHook)(void);
extern const char* FLIPPER_AUTORUN_APP_NAME;
/* Services list
* Spawned on startup
*/
extern const FlipperApplication FLIPPER_SERVICES[];
extern const FlipperInternalApplication FLIPPER_SERVICES[];
extern const size_t FLIPPER_SERVICES_COUNT;
/* Apps list
* Spawned by loader
*/
extern const FlipperApplication FLIPPER_APPS[];
extern const FlipperInternalApplication FLIPPER_APPS[];
extern const size_t FLIPPER_APPS_COUNT;
/* On system start hooks
* 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;
/* System apps
* 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;
/* Separate scene app holder
* 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;
extern const FlipperInternalApplication FLIPPER_ARCHIVE;
/* Settings list
* Spawned by loader
*/
extern const FlipperApplication FLIPPER_SETTINGS_APPS[];
extern const FlipperInternalApplication FLIPPER_SETTINGS_APPS[];
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);
}
}
static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
furi_assert(canvas);

View file

@ -16,8 +16,6 @@
#define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.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) {
furi_assert(context);
Desktop* desktop = context;
@ -40,7 +38,8 @@ static void desktop_scene_main_interact_animation_callback(void* context) {
}
#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(flipper_app);
furi_assert(flipper_app->app);
@ -67,30 +66,16 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl
#endif
static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) {
do {
LoaderStatus status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, path);
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);
if(loader_start_with_gui_error(desktop->loader, path, NULL) != LoaderStatusOk) {
loader_start(desktop->loader, "Passport", NULL, NULL);
}
}
static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) {
LoaderStatus status = LoaderStatusErrorInternal;
if(application->is_external) {
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);
if(strlen(application->name_or_path) > 0) {
loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL);
} else {
status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, NULL);
}
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL);
}
}
@ -148,10 +133,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
break;
case DesktopMainEventOpenPowerOff: {
LoaderStatus status = loader_start(desktop->loader, "Power", "off");
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
loader_start(desktop->loader, "Power", "off", NULL);
consumed = true;
break;
}
@ -176,18 +158,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
break;
case DesktopAnimationEventInteractAnimation:
if(!animation_manager_interact_process(desktop->animation_manager)) {
LoaderStatus status = loader_start(desktop->loader, "Passport", NULL);
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
loader_start(desktop->loader, "Passport", NULL, NULL);
}
consumed = true;
break;
case DesktopMainEventOpenPassport: {
LoaderStatus status = loader_start(desktop->loader, "Passport", NULL);
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
loader_start(desktop->loader, "Passport", NULL, NULL);
break;
}
case DesktopMainEventOpenGame: {

View file

@ -1,7 +1,7 @@
#pragma once
#include "dialogs.h"
#include "dialogs_message.h"
#include "view_holder.h"
#include <gui/view_holder.h>
#include <gui/modules/file_browser.h>
#ifdef __cplusplus

View file

@ -1,20 +1,27 @@
#include "loader.h"
#include "loader_i.h"
#include "loader_menu.h"
#include <applications.h>
#include <storage/storage.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 LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF
// 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;
LoaderMessageLoaderStatusResult result;
message.type = LoaderMessageTypeStartByName;
message.start.name = name;
message.start.args = args;
message.start.error_message = error_message;
message.api_lock = api_lock_alloc_locked();
message.status_value = &result;
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;
}
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) {
LoaderMessage message;
LoaderMessageBoolResult result;
@ -73,27 +105,26 @@ static void loader_menu_closed_callback(void* context) {
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_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) {
furi_assert(context);
Loader* loader = context;
LoaderEvent event;
if(thread_state == FuriThreadStateRunning) {
LoaderEvent event;
event.type = LoaderEventTypeApplicationStarted;
furi_pubsub_publish(loader->pubsub, &event);
} else if(thread_state == FuriThreadStateStopped) {
LoaderMessage message;
message.type = LoaderMessageTypeAppClosed;
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->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
loader->loader_menu = NULL;
loader->loader_applications = NULL;
loader->app.args = NULL;
loader->app.name = NULL;
loader->app.thread = NULL;
loader->app.insomniac = false;
loader->app.fap = NULL;
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 FlipperApplication* list,
const FlipperInternalApplication* list,
const uint32_t n_apps) {
for(size_t i = 0; i < n_apps; i++) {
if(strcmp(name, list[i].name) == 0) {
@ -123,8 +155,8 @@ static FlipperApplication const* loader_find_application_by_name_in_list(
return NULL;
}
static const FlipperApplication* loader_find_application_by_name(const char* name) {
const FlipperApplication* application = NULL;
static const FlipperInternalApplication* loader_find_application_by_name(const char* name) {
const FlipperInternalApplication* application = NULL;
application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT);
if(!application) {
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;
}
static void
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);
static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) {
// setup heap trace
FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
if(mode > FuriHalRtcHeapTrackModeNone) {
@ -166,14 +180,14 @@ static void
}
// setup insomnia
if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) {
if(!(flags & FlipperInternalApplicationFlagInsomniaSafe)) {
furi_hal_power_insomnia_enter();
loader->app.insomniac = true;
} else {
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_callback(loader->app.thread, loader_thread_state_callback);
@ -181,42 +195,206 @@ static void
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
static void loader_do_menu_show(Loader* loader) {
if(!loader->loader_menu) {
loader->loader_menu = loader_menu_alloc();
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);
loader->loader_menu = loader_menu_alloc(loader_menu_closed_callback, loader);
}
}
static void loader_do_menu_closed(Loader* loader) {
if(loader->loader_menu) {
loader_menu_stop(loader->loader_menu);
loader_menu_free(loader->loader_menu);
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) {
return loader->app.thread != NULL;
}
static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) {
if(loader_do_is_locked(loader)) {
return LoaderStatusErrorAppStarted;
}
static LoaderStatus loader_do_start_by_name(
Loader* loader,
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) {
return LoaderStatusErrorUnknownApp;
}
// check Applications
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);
return LoaderStatusOk;
// check external apps
{
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) {
@ -229,13 +407,16 @@ static bool loader_do_lock(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;
}
static void loader_do_app_closed(Loader* loader) {
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) {
free(loader->app.args);
loader->app.args = NULL;
@ -245,12 +426,20 @@ static void loader_do_app_closed(Loader* loader) {
furi_hal_power_insomnia_exit();
}
free(loader->app.name);
loader->app.name = NULL;
if(loader->app.fap) {
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_thread_free(loader->app.thread);
loader->app.thread = NULL;
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
LoaderEvent event;
event.type = LoaderEventTypeApplicationStopped;
furi_pubsub_publish(loader->pubsub, &event);
}
// app
@ -266,7 +455,7 @@ int32_t loader_srv(void* p) {
}
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;
@ -274,8 +463,8 @@ int32_t loader_srv(void* p) {
if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
switch(message.type) {
case LoaderMessageTypeStartByName:
message.status_value->value =
loader_do_start_by_name(loader, message.start.name, message.start.args);
message.status_value->value = loader_do_start_by_name(
loader, message.start.name, message.start.args, message.start.error_message);
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeShowMenu:
@ -297,6 +486,10 @@ int32_t loader_srv(void* p) {
break;
case LoaderMessageTypeUnlock:
loader_do_unlock(loader);
break;
case LoaderMessageTypeApplicationsClosed:
loader_do_applications_closed(loader);
break;
}
}
}

View file

@ -6,6 +6,7 @@ extern "C" {
#endif
#define RECORD_LOADER "loader"
#define LOADER_APPLICATIONS_NAME "Applications"
typedef struct Loader Loader;
@ -25,28 +26,57 @@ typedef struct {
LoaderEventType type;
} LoaderEvent;
/** Start application
* @param name - application name
* @param args - application arguments
* @retval true on success
/**
* @brief Start application
* @param[in] instance loader instance
* @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);
/** Unlock application start */
/**
* @brief Unlock application start
* @param[in] instance 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);
/** Show primary loader */
/**
* @brief Show loader menu
* @param[in] instance 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);
#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);
LoaderStatus status = loader_start(loader, app_name_str, args_str);
switch(status) {
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;
FuriString* error_message = furi_string_alloc();
if(loader_start(loader, app_name_str, args_str, error_message) != LoaderStatusOk) {
printf("%s\r\n", furi_string_get_cstr(error_message));
}
furi_string_free(error_message);
} while(false);
furi_string_free(app_name);

View file

@ -1,20 +1,23 @@
#pragma once
#include <furi.h>
#include <toolbox/api_lock.h>
#include <flipper_application/flipper_application.h>
#include "loader.h"
#include "loader_menu.h"
#include "loader_applications.h"
typedef struct {
char* args;
char* name;
FuriThread* thread;
bool insomniac;
FlipperApplication* fap;
} LoaderAppData;
struct Loader {
FuriPubSub* pubsub;
FuriMessageQueue* queue;
LoaderMenu* loader_menu;
LoaderApplications* loader_applications;
LoaderAppData app;
};
@ -23,6 +26,7 @@ typedef enum {
LoaderMessageTypeAppClosed,
LoaderMessageTypeShowMenu,
LoaderMessageTypeMenuClosed,
LoaderMessageTypeApplicationsClosed,
LoaderMessageTypeLock,
LoaderMessageTypeUnlock,
LoaderMessageTypeIsLocked,
@ -31,6 +35,7 @@ typedef enum {
typedef struct {
const char* name;
const char* args;
FuriString* error_message;
} LoaderMessageStartByName;
typedef struct {

View file

@ -5,106 +5,76 @@
#include <assets_icons.h>
#include <applications.h>
#include "loader.h"
#include "loader_menu.h"
#define TAG "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;
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 {
LoaderMenuViewPrimary,
LoaderMenuViewSettings,
} 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() {
LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu));
loader_menu->gui = furi_record_open(RECORD_GUI);
loader_menu->view_dispatcher = view_dispatcher_alloc();
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_start(const char* name) {
Loader* loader = furi_record_open(RECORD_LOADER);
loader_start_with_gui_error(loader, name, NULL);
furi_record_close(RECORD_LOADER);
}
static void loader_menu_callback(void* context, uint32_t index) {
LoaderMenu* loader_menu = context;
UNUSED(context);
const char* name = FLIPPER_APPS[index].name;
if(loader_menu->click_callback) {
loader_menu->click_callback(name, loader_menu->click_callback_context);
}
loader_menu_start(name);
}
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) {
LoaderMenu* loader_menu = context;
UNUSED(context);
const char* name = FLIPPER_SETTINGS_APPS[index].name;
if(loader_menu->click_callback) {
loader_menu->click_callback(name, loader_menu->click_callback_context);
}
loader_menu_start(name);
}
static void loader_menu_switch_to_settings(void* context, uint32_t index) {
UNUSED(index);
LoaderMenu* loader_menu = context;
view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewSettings);
LoaderMenuApp* app = context;
view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewSettings);
}
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;
}
static void loader_menu_build_menu(LoaderMenu* loader_menu) {
static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) {
size_t i;
for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
menu_add_item(
loader_menu->primary_menu,
app->primary_menu,
FLIPPER_APPS[i].name,
FLIPPER_APPS[i].icon,
i,
loader_menu_callback,
(void*)loader_menu);
(void*)menu);
}
menu_add_item(
loader_menu->primary_menu,
"Settings",
&A_Settings_14,
app->primary_menu, "Settings", &A_Settings_14, i++, loader_menu_switch_to_settings, app);
menu_add_item(
app->primary_menu,
LOADER_APPLICATIONS_NAME,
&A_Plugins_14,
i++,
loader_menu_switch_to_settings,
loader_menu);
loader_menu_applications_callback,
(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++) {
submenu_add_item(
loader_menu->settings_menu,
app->settings_menu,
FLIPPER_SETTINGS_APPS[i].name,
i,
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) {
LoaderMenu* loader_menu = p;
furi_assert(loader_menu);
loader_menu_build_menu(loader_menu);
loader_menu_build_submenu(loader_menu);
LoaderMenuApp* app = loader_menu_app_alloc(loader_menu);
view_dispatcher_attach_to_gui(
loader_menu->view_dispatcher, loader_menu->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_run(app->view_dispatcher);
// Primary menu
View* primary_view = menu_get_view(loader_menu->primary_menu);
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);
if(loader_menu->closed_cb) {
loader_menu->closed_cb(loader_menu->context);
}
loader_menu_app_free(app);
return 0;
}

View file

@ -7,24 +7,10 @@ extern "C" {
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_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
}
#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);
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) {
result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED;
} else if(status == LoaderStatusErrorInternal) {

View file

@ -3,7 +3,6 @@
#include "desktop_settings_scene.h"
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <fap_loader/fap_loader_app.h>
#define EXTERNAL_APPLICATION_NAME ("[External Application]")
#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,
EXTERNAL_APPLICATION_NAME,
@ -75,7 +73,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
if(curr_favorite_app->is_external) {
pre_select_item = EXTERNAL_APPLICATION_INDEX;
}
#endif
submenu_set_header(
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) {
Loader* loader = furi_record_open(RECORD_LOADER);
loader_start(loader, "StorageMoveToSd", NULL);
loader_start(loader, "StorageMoveToSd", NULL, NULL);
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
*/
Loader* loader = furi_record_open(RECORD_LOADER);
loader_start(loader, "UpdaterApp", NULL);
loader_start(loader, "UpdaterApp", NULL, NULL);
furi_record_close(RECORD_LOADER);
}

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,30.1,,
Version,+,31.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.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,-,fiscanf,int,"FILE*, const char*, ..."
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_get_manifest,const FlipperApplicationManifest*,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_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*"
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_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*"
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_close,_Bool,FlipperFormat*
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_lock,_Bool,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,+,loading_alloc,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
Version,+,30.1,,
Version,+,31.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@ -892,9 +892,11 @@ Function,-,finitel,int,long double
Function,-,fiprintf,int,"FILE*, const char*, ..."
Function,-,fiscanf,int,"FILE*, const char*, ..."
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_get_manifest,const FlipperApplicationManifest*,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_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*"
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_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*"
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_close,_Bool,FlipperFormat*
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_lock,_Bool,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,+,loading_alloc,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 <notification/notification_messages.h>
#include "application_assets.h"
#include <loader/firmware_api/firmware_api.h>
#include <m-list.h>
@ -81,6 +82,12 @@ void flipper_application_free(FlipperApplication* app) {
}
elf_file_free(app->elf);
if(app->ep_thread_args) {
free(app->ep_thread_args);
app->ep_thread_args = NULL;
}
free(app);
}
@ -224,10 +231,19 @@ static int32_t flipper_application_thread(void* context) {
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(!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);
app->thread = furi_thread_alloc_ex(
@ -289,4 +305,32 @@ const FlipperAppPluginDescriptor*
lib_descriptor->ep_api_version);
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);
/**
* @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.
* Can be only called once for application instance.
* @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
*/
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)
@ -149,6 +149,21 @@ typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)(
const FlipperAppPluginDescriptor*
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
}
#endif

View file

@ -52,9 +52,7 @@ class Main(App):
if not self.args.launch_app:
return 0
storage.send_and_wait_eol(
f'loader open "Applications" {fap_dst_path}\r'
)
storage.send_and_wait_eol(f"loader open {fap_dst_path}\r")
if len(result := storage.read.until(storage.CLI_EOL)):
self.logger.error(f"Unexpected response: {result.decode('ascii')}")

View file

@ -353,12 +353,18 @@ class AppBuildset:
class ApplicationsCGenerator:
APP_TYPE_MAP = {
FlipperAppType.SERVICE: ("FlipperApplication", "FLIPPER_SERVICES"),
FlipperAppType.SYSTEM: ("FlipperApplication", "FLIPPER_SYSTEM_APPS"),
FlipperAppType.APP: ("FlipperApplication", "FLIPPER_APPS"),
FlipperAppType.DEBUG: ("FlipperApplication", "FLIPPER_DEBUG_APPS"),
FlipperAppType.SETTINGS: ("FlipperApplication", "FLIPPER_SETTINGS_APPS"),
FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"),
FlipperAppType.SERVICE: ("FlipperInternalApplication", "FLIPPER_SERVICES"),
FlipperAppType.SYSTEM: ("FlipperInternalApplication", "FLIPPER_SYSTEM_APPS"),
FlipperAppType.APP: ("FlipperInternalApplication", "FLIPPER_APPS"),
FlipperAppType.DEBUG: ("FlipperInternalApplication", "FLIPPER_DEBUG_APPS"),
FlipperAppType.SETTINGS: (
"FlipperInternalApplication",
"FLIPPER_SETTINGS_APPS",
),
FlipperAppType.STARTUP: (
"FlipperInternalOnStartHook",
"FLIPPER_ON_SYSTEM_START",
),
}
def __init__(self, buildset: AppBuildset, autorun_app: str = ""):
@ -379,7 +385,7 @@ class ApplicationsCGenerator:
.appid = "{app.appid}",
.stack_size = {app.stack_size},
.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):
contents = [
@ -408,7 +414,7 @@ class ApplicationsCGenerator:
contents.extend(
[
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)
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:
startup_command = self.args.host_app