mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-10 15:04:19 +00:00
[FL-2837][FL-3270] Loader refaptoring: second encounter (#2779)
* Core: rename internal FlipperApplication to FlipperInternalApplication * FAP Loader: move load_name_and_icon to flipper_application library * Loader menu: rework api * View holder: move to gui service * Loader: simple "loading" worker * Loader: applications dialog * Loader: fapping * Update f18 api * Apps: remove fap_loader * Libs, flipper application: store args, rename thread allocation * Loader: error handling * Apps: use loader error handling * Loader: documentation * FBT: accomodate loader * Loader: do not raise gui error if loader is locked * Archive: accomodate loader * Loader: fix loading message * Flipper: drop some old dolphin legacy * Loader: generalize error construction Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
parent
4ddfe05a59
commit
761a14e6e2
32 changed files with 723 additions and 581 deletions
|
@ -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
|
||||
|
|
|
@ -12,7 +12,6 @@ App(
|
|||
"subghz",
|
||||
"bad_usb",
|
||||
"u2f",
|
||||
"fap_loader",
|
||||
"archive",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
App(
|
||||
appid="fap_loader",
|
||||
name="Applications",
|
||||
apptype=FlipperAppType.APP,
|
||||
entry_point="fap_loader_app",
|
||||
cdefines=["APP_FAP_LOADER"],
|
||||
requires=[
|
||||
"gui",
|
||||
"storage",
|
||||
"loader",
|
||||
],
|
||||
stack_size=int(1.5 * 1024),
|
||||
icon="A_Plugins_14",
|
||||
order=90,
|
||||
)
|
|
@ -1,216 +0,0 @@
|
|||
#include "fap_loader_app.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_debug.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
|
||||
#define TAG "FapLoader"
|
||||
|
||||
struct FapLoader {
|
||||
FlipperApplication* app;
|
||||
Storage* storage;
|
||||
DialogsApp* dialogs;
|
||||
Gui* gui;
|
||||
FuriString* fap_path;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Loading* loading;
|
||||
};
|
||||
|
||||
bool fap_loader_load_name_and_icon(
|
||||
FuriString* path,
|
||||
Storage* storage,
|
||||
uint8_t** icon_ptr,
|
||||
FuriString* item_name) {
|
||||
FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface);
|
||||
|
||||
FlipperApplicationPreloadStatus preload_res =
|
||||
flipper_application_preload_manifest(app, furi_string_get_cstr(path));
|
||||
|
||||
bool load_success = false;
|
||||
|
||||
if(preload_res == FlipperApplicationPreloadStatusSuccess) {
|
||||
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
|
||||
if(manifest->has_icon) {
|
||||
memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
}
|
||||
furi_string_set(item_name, manifest->name);
|
||||
load_success = true;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "FAP Loader failed to preload %s", furi_string_get_cstr(path));
|
||||
load_success = false;
|
||||
}
|
||||
|
||||
flipper_application_free(app);
|
||||
return load_success;
|
||||
}
|
||||
|
||||
static bool fap_loader_item_callback(
|
||||
FuriString* path,
|
||||
void* context,
|
||||
uint8_t** icon_ptr,
|
||||
FuriString* item_name) {
|
||||
FapLoader* fap_loader = context;
|
||||
furi_assert(fap_loader);
|
||||
return fap_loader_load_name_and_icon(path, fap_loader->storage, icon_ptr, item_name);
|
||||
}
|
||||
|
||||
static bool fap_loader_run_selected_app(FapLoader* loader) {
|
||||
furi_assert(loader);
|
||||
|
||||
FuriString* error_message;
|
||||
|
||||
error_message = furi_string_alloc_set("unknown error");
|
||||
|
||||
bool file_selected = false;
|
||||
bool show_error = true;
|
||||
do {
|
||||
file_selected = true;
|
||||
loader->app = flipper_application_alloc(loader->storage, firmware_api_interface);
|
||||
size_t start = furi_get_tick();
|
||||
|
||||
FURI_LOG_I(TAG, "FAP Loader is loading %s", furi_string_get_cstr(loader->fap_path));
|
||||
|
||||
FlipperApplicationPreloadStatus preload_res =
|
||||
flipper_application_preload(loader->app, furi_string_get_cstr(loader->fap_path));
|
||||
if(preload_res != FlipperApplicationPreloadStatusSuccess) {
|
||||
const char* err_msg = flipper_application_preload_status_to_string(preload_res);
|
||||
furi_string_printf(error_message, "Preload failed: %s", err_msg);
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"FAP Loader failed to preload %s: %s",
|
||||
furi_string_get_cstr(loader->fap_path),
|
||||
err_msg);
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "FAP Loader is mapping");
|
||||
FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(loader->app);
|
||||
if(load_status != FlipperApplicationLoadStatusSuccess) {
|
||||
const char* err_msg = flipper_application_load_status_to_string(load_status);
|
||||
furi_string_printf(error_message, "Load failed: %s", err_msg);
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"FAP Loader failed to map to memory %s: %s",
|
||||
furi_string_get_cstr(loader->fap_path),
|
||||
err_msg);
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
|
||||
FURI_LOG_I(TAG, "FAP Loader is starting app");
|
||||
|
||||
FuriThread* thread = flipper_application_spawn(loader->app, NULL);
|
||||
|
||||
/* This flag is set by the debugger - to break on app start */
|
||||
if(furi_hal_debug_is_gdb_session_active()) {
|
||||
FURI_LOG_W(TAG, "Triggering BP for debugger");
|
||||
/* After hitting this, you can set breakpoints in your .fap's code
|
||||
* Note that you have to toggle breakpoints that were set before */
|
||||
__asm volatile("bkpt 0");
|
||||
}
|
||||
|
||||
FuriString* app_name = furi_string_alloc();
|
||||
path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name);
|
||||
furi_thread_set_appid(thread, furi_string_get_cstr(app_name));
|
||||
furi_string_free(app_name);
|
||||
|
||||
furi_thread_start(thread);
|
||||
furi_thread_join(thread);
|
||||
|
||||
show_error = false;
|
||||
int ret = furi_thread_get_return_code(thread);
|
||||
|
||||
FURI_LOG_I(TAG, "FAP app returned: %i", ret);
|
||||
} while(0);
|
||||
|
||||
if(show_error) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_message_set_buttons(message, NULL, NULL, NULL);
|
||||
|
||||
FuriString* buffer;
|
||||
buffer = furi_string_alloc();
|
||||
furi_string_printf(buffer, "%s", furi_string_get_cstr(error_message));
|
||||
furi_string_replace(buffer, ":", "\n");
|
||||
dialog_message_set_text(
|
||||
message, furi_string_get_cstr(buffer), 64, 32, AlignCenter, AlignCenter);
|
||||
|
||||
dialog_message_show(loader->dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(buffer);
|
||||
}
|
||||
|
||||
furi_string_free(error_message);
|
||||
|
||||
if(file_selected) {
|
||||
flipper_application_free(loader->app);
|
||||
}
|
||||
|
||||
return file_selected;
|
||||
}
|
||||
|
||||
static bool fap_loader_select_app(FapLoader* loader) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap",
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
.item_loader_callback = fap_loader_item_callback,
|
||||
.item_loader_context = loader,
|
||||
.base_path = EXT_PATH("apps"),
|
||||
};
|
||||
|
||||
return dialog_file_browser_show(
|
||||
loader->dialogs, loader->fap_path, loader->fap_path, &browser_options);
|
||||
}
|
||||
|
||||
static FapLoader* fap_loader_alloc(const char* path) {
|
||||
FapLoader* loader = malloc(sizeof(FapLoader)); //-V799
|
||||
loader->fap_path = furi_string_alloc_set(path);
|
||||
loader->storage = furi_record_open(RECORD_STORAGE);
|
||||
loader->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
loader->gui = furi_record_open(RECORD_GUI);
|
||||
loader->view_dispatcher = view_dispatcher_alloc();
|
||||
loader->loading = loading_alloc();
|
||||
view_dispatcher_attach_to_gui(
|
||||
loader->view_dispatcher, loader->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_add_view(loader->view_dispatcher, 0, loading_get_view(loader->loading));
|
||||
return loader;
|
||||
} //-V773
|
||||
|
||||
static void fap_loader_free(FapLoader* loader) {
|
||||
view_dispatcher_remove_view(loader->view_dispatcher, 0);
|
||||
loading_free(loader->loading);
|
||||
view_dispatcher_free(loader->view_dispatcher);
|
||||
furi_string_free(loader->fap_path);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
free(loader);
|
||||
}
|
||||
|
||||
int32_t fap_loader_app(void* p) {
|
||||
FapLoader* loader;
|
||||
if(p) {
|
||||
loader = fap_loader_alloc((const char*)p);
|
||||
view_dispatcher_switch_to_view(loader->view_dispatcher, 0);
|
||||
fap_loader_run_selected_app(loader);
|
||||
} else {
|
||||
loader = fap_loader_alloc(EXT_PATH("apps"));
|
||||
while(fap_loader_select_app(loader)) {
|
||||
view_dispatcher_switch_to_view(loader->view_dispatcher, 0);
|
||||
fap_loader_run_selected_app(loader);
|
||||
};
|
||||
}
|
||||
|
||||
fap_loader_free(loader);
|
||||
return 0;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
#include <storage/storage.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FapLoader FapLoader;
|
||||
|
||||
/**
|
||||
* @brief Load name and icon from FAP file.
|
||||
*
|
||||
* @param path Path to FAP file.
|
||||
* @param storage Storage instance.
|
||||
* @param icon_ptr Icon pointer.
|
||||
* @param item_name Application name.
|
||||
* @return true if icon and name were loaded successfully.
|
||||
*/
|
||||
bool fap_loader_load_name_and_icon(
|
||||
FuriString* path,
|
||||
Storage* storage,
|
||||
uint8_t** icon_ptr,
|
||||
FuriString* item_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -4,9 +4,9 @@
|
|||
#include <gui/icon.h>
|
||||
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
146
applications/services/loader/loader_applications.c
Normal file
146
applications/services/loader/loader_applications.c
Normal file
|
@ -0,0 +1,146 @@
|
|||
#include "loader.h"
|
||||
#include "loader_applications.h"
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/loading.h>
|
||||
|
||||
#define TAG "LoaderApplications"
|
||||
|
||||
struct LoaderApplications {
|
||||
FuriThread* thread;
|
||||
void (*closed_cb)(void*);
|
||||
void* context;
|
||||
};
|
||||
|
||||
static int32_t loader_applications_thread(void* p);
|
||||
|
||||
LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context) {
|
||||
LoaderApplications* loader_applications = malloc(sizeof(LoaderApplications));
|
||||
loader_applications->thread =
|
||||
furi_thread_alloc_ex(TAG, 512, loader_applications_thread, (void*)loader_applications);
|
||||
loader_applications->closed_cb = closed_cb;
|
||||
loader_applications->context = context;
|
||||
furi_thread_start(loader_applications->thread);
|
||||
return loader_applications;
|
||||
}
|
||||
|
||||
void loader_applications_free(LoaderApplications* loader_applications) {
|
||||
furi_assert(loader_applications);
|
||||
furi_thread_join(loader_applications->thread);
|
||||
furi_thread_free(loader_applications->thread);
|
||||
free(loader_applications);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
FuriString* fap_path;
|
||||
DialogsApp* dialogs;
|
||||
Storage* storage;
|
||||
} LoaderApplicationsApp;
|
||||
|
||||
static LoaderApplicationsApp* loader_applications_app_alloc() {
|
||||
LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799
|
||||
app->fap_path = furi_string_alloc_set(EXT_PATH("apps"));
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
return app;
|
||||
} //-V773
|
||||
|
||||
static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) {
|
||||
furi_assert(loader_applications_app);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(loader_applications_app->fap_path);
|
||||
free(loader_applications_app);
|
||||
}
|
||||
|
||||
static bool loader_applications_item_callback(
|
||||
FuriString* path,
|
||||
void* context,
|
||||
uint8_t** icon_ptr,
|
||||
FuriString* item_name) {
|
||||
LoaderApplicationsApp* loader_applications_app = context;
|
||||
furi_assert(loader_applications_app);
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
}
|
||||
|
||||
static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap",
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
.item_loader_callback = loader_applications_item_callback,
|
||||
.item_loader_context = loader_applications_app,
|
||||
.base_path = EXT_PATH("apps"),
|
||||
};
|
||||
|
||||
return dialog_file_browser_show(
|
||||
loader_applications_app->dialogs,
|
||||
loader_applications_app->fap_path,
|
||||
loader_applications_app->fap_path,
|
||||
&browser_options);
|
||||
}
|
||||
|
||||
#define APPLICATION_STOP_EVENT 1
|
||||
|
||||
static void loader_pubsub_callback(const void* message, void* context) {
|
||||
const LoaderEvent* event = message;
|
||||
const FuriThreadId thread_id = (FuriThreadId)context;
|
||||
|
||||
if(event->type == LoaderEventTypeApplicationStopped) {
|
||||
furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT);
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_applications_start_app(const char* name) {
|
||||
// start loading animation
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
Loading* loading = loading_alloc();
|
||||
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
view_holder_set_view(view_holder, loading_get_view(loading));
|
||||
view_holder_start(view_holder);
|
||||
|
||||
// load app
|
||||
FuriThreadId thread_id = furi_thread_get_current_id();
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
FuriPubSubSubscription* subscription =
|
||||
furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id);
|
||||
|
||||
LoaderStatus status = loader_start_with_gui_error(loader, name, NULL);
|
||||
|
||||
if(status == LoaderStatusOk) {
|
||||
furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
}
|
||||
|
||||
furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
|
||||
// stop loading animation
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_free(view_holder);
|
||||
loading_free(loading);
|
||||
furi_record_close(RECORD_GUI);
|
||||
}
|
||||
|
||||
static int32_t loader_applications_thread(void* p) {
|
||||
LoaderApplications* loader_applications = p;
|
||||
LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc();
|
||||
|
||||
while(loader_applications_select_app(loader_applications_app)) {
|
||||
loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path));
|
||||
}
|
||||
|
||||
loader_applications_app_free(loader_applications_app);
|
||||
|
||||
if(loader_applications->closed_cb) {
|
||||
loader_applications->closed_cb(loader_applications->context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
16
applications/services/loader/loader_applications.h
Normal file
16
applications/services/loader/loader_applications.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct LoaderApplications LoaderApplications;
|
||||
|
||||
LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context);
|
||||
|
||||
void loader_applications_free(LoaderApplications* loader_applications);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -50,21 +50,11 @@ static void loader_cli_open(FuriString* args, Loader* loader) {
|
|||
|
||||
const char* app_name_str = furi_string_get_cstr(app_name);
|
||||
|
||||
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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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:");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,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*
|
||||
|
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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')}")
|
||||
|
|
|
@ -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])};",
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue