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