mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
[FL-2837][FL-3270] Loader refaptoring: second encounter (#2779)
* Core: rename internal FlipperApplication to FlipperInternalApplication * FAP Loader: move load_name_and_icon to flipper_application library * Loader menu: rework api * View holder: move to gui service * Loader: simple "loading" worker * Loader: applications dialog * Loader: fapping * Update f18 api * Apps: remove fap_loader * Libs, flipper application: store args, rename thread allocation * Loader: error handling * Apps: use loader error handling * Loader: documentation * FBT: accomodate loader * Loader: do not raise gui error if loader is locked * Archive: accomodate loader * Loader: fix loading message * Flipper: drop some old dolphin legacy * Loader: generalize error construction Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
@@ -1,20 +1,27 @@
|
||||
#include "loader.h"
|
||||
#include "loader_i.h"
|
||||
#include "loader_menu.h"
|
||||
#include <applications.h>
|
||||
#include <storage/storage.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
|
||||
#define TAG "Loader"
|
||||
#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF
|
||||
// api
|
||||
|
||||
LoaderStatus loader_start(Loader* loader, const char* name, const char* args) {
|
||||
LoaderStatus
|
||||
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageLoaderStatusResult result;
|
||||
|
||||
message.type = LoaderMessageTypeStartByName;
|
||||
message.start.name = name;
|
||||
message.start.args = args;
|
||||
message.start.error_message = error_message;
|
||||
message.api_lock = api_lock_alloc_locked();
|
||||
message.status_value = &result;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
@@ -22,6 +29,31 @@ LoaderStatus loader_start(Loader* loader, const char* name, const char* args) {
|
||||
return result.value;
|
||||
}
|
||||
|
||||
LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) {
|
||||
FuriString* error_message = furi_string_alloc();
|
||||
LoaderStatus status = loader_start(loader, name, args, error_message);
|
||||
|
||||
// TODO: we have many places where we can emit a double start, ex: desktop, menu
|
||||
// so i prefer to not show LoaderStatusErrorAppStarted error message for now
|
||||
if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) {
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_message_set_buttons(message, NULL, NULL, NULL);
|
||||
|
||||
furi_string_replace(error_message, ":", "\n");
|
||||
dialog_message_set_text(
|
||||
message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter);
|
||||
|
||||
dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
}
|
||||
|
||||
furi_string_free(error_message);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool loader_lock(Loader* loader) {
|
||||
LoaderMessage message;
|
||||
LoaderMessageBoolResult result;
|
||||
@@ -73,27 +105,26 @@ static void loader_menu_closed_callback(void* context) {
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void loader_menu_click_callback(const char* name, void* context) {
|
||||
static void loader_applications_closed_callback(void* context) {
|
||||
Loader* loader = context;
|
||||
loader_start(loader, name, NULL);
|
||||
LoaderMessage message;
|
||||
message.type = LoaderMessageTypeApplicationsClosed;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Loader* loader = context;
|
||||
LoaderEvent event;
|
||||
|
||||
if(thread_state == FuriThreadStateRunning) {
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationStarted;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
} else if(thread_state == FuriThreadStateStopped) {
|
||||
LoaderMessage message;
|
||||
message.type = LoaderMessageTypeAppClosed;
|
||||
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
|
||||
|
||||
event.type = LoaderEventTypeApplicationStopped;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,16 +135,17 @@ static Loader* loader_alloc() {
|
||||
loader->pubsub = furi_pubsub_alloc();
|
||||
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
|
||||
loader->loader_menu = NULL;
|
||||
loader->loader_applications = NULL;
|
||||
loader->app.args = NULL;
|
||||
loader->app.name = NULL;
|
||||
loader->app.thread = NULL;
|
||||
loader->app.insomniac = false;
|
||||
loader->app.fap = NULL;
|
||||
return loader;
|
||||
}
|
||||
|
||||
static FlipperApplication const* loader_find_application_by_name_in_list(
|
||||
static FlipperInternalApplication const* loader_find_application_by_name_in_list(
|
||||
const char* name,
|
||||
const FlipperApplication* list,
|
||||
const FlipperInternalApplication* list,
|
||||
const uint32_t n_apps) {
|
||||
for(size_t i = 0; i < n_apps; i++) {
|
||||
if(strcmp(name, list[i].name) == 0) {
|
||||
@@ -123,8 +155,8 @@ static FlipperApplication const* loader_find_application_by_name_in_list(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const FlipperApplication* loader_find_application_by_name(const char* name) {
|
||||
const FlipperApplication* application = NULL;
|
||||
static const FlipperInternalApplication* loader_find_application_by_name(const char* name) {
|
||||
const FlipperInternalApplication* application = NULL;
|
||||
application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT);
|
||||
if(!application) {
|
||||
application = loader_find_application_by_name_in_list(
|
||||
@@ -138,25 +170,7 @@ static const FlipperApplication* loader_find_application_by_name(const char* nam
|
||||
return application;
|
||||
}
|
||||
|
||||
static void
|
||||
loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) {
|
||||
FURI_LOG_I(TAG, "Starting %s", app->name);
|
||||
|
||||
// store args
|
||||
furi_assert(loader->app.args == NULL);
|
||||
if(args && strlen(args) > 0) {
|
||||
loader->app.args = strdup(args);
|
||||
}
|
||||
|
||||
// store name
|
||||
furi_assert(loader->app.name == NULL);
|
||||
loader->app.name = strdup(app->name);
|
||||
|
||||
// setup app thread
|
||||
loader->app.thread =
|
||||
furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args);
|
||||
furi_thread_set_appid(loader->app.thread, app->appid);
|
||||
|
||||
static void loader_start_app_thread(Loader* loader, FlipperInternalApplicationFlag flags) {
|
||||
// setup heap trace
|
||||
FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
|
||||
if(mode > FuriHalRtcHeapTrackModeNone) {
|
||||
@@ -166,14 +180,14 @@ static void
|
||||
}
|
||||
|
||||
// setup insomnia
|
||||
if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) {
|
||||
if(!(flags & FlipperInternalApplicationFlagInsomniaSafe)) {
|
||||
furi_hal_power_insomnia_enter();
|
||||
loader->app.insomniac = true;
|
||||
} else {
|
||||
loader->app.insomniac = false;
|
||||
}
|
||||
|
||||
// setup app thread callbacks
|
||||
// setup thread state callbacks
|
||||
furi_thread_set_state_context(loader->app.thread, loader);
|
||||
furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback);
|
||||
|
||||
@@ -181,42 +195,206 @@ static void
|
||||
furi_thread_start(loader->app.thread);
|
||||
}
|
||||
|
||||
static void loader_start_internal_app(
|
||||
Loader* loader,
|
||||
const FlipperInternalApplication* app,
|
||||
const char* args) {
|
||||
FURI_LOG_I(TAG, "Starting %s", app->name);
|
||||
|
||||
// store args
|
||||
furi_assert(loader->app.args == NULL);
|
||||
if(args && strlen(args) > 0) {
|
||||
loader->app.args = strdup(args);
|
||||
}
|
||||
|
||||
loader->app.thread =
|
||||
furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args);
|
||||
furi_thread_set_appid(loader->app.thread, app->appid);
|
||||
|
||||
loader_start_app_thread(loader, app->flags);
|
||||
}
|
||||
|
||||
static void loader_log_status_error(
|
||||
LoaderStatus status,
|
||||
FuriString* error_message,
|
||||
const char* format,
|
||||
va_list args) {
|
||||
if(error_message) {
|
||||
furi_string_vprintf(error_message, format, args);
|
||||
FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(error_message));
|
||||
} else {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
FURI_LOG_E(TAG, "Status [%d]: %s", status, furi_string_get_cstr(tmp));
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
static LoaderStatus loader_make_status_error(
|
||||
LoaderStatus status,
|
||||
FuriString* error_message,
|
||||
const char* format,
|
||||
...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
loader_log_status_error(status, error_message, format, args);
|
||||
va_end(args);
|
||||
return status;
|
||||
}
|
||||
|
||||
static LoaderStatus loader_make_success_status(FuriString* error_message) {
|
||||
if(error_message) {
|
||||
furi_string_set(error_message, "App started");
|
||||
}
|
||||
|
||||
return LoaderStatusOk;
|
||||
}
|
||||
|
||||
static LoaderStatus loader_start_external_app(
|
||||
Loader* loader,
|
||||
Storage* storage,
|
||||
const char* path,
|
||||
const char* args,
|
||||
FuriString* error_message) {
|
||||
LoaderStatus status = loader_make_success_status(error_message);
|
||||
|
||||
do {
|
||||
loader->app.fap = flipper_application_alloc(storage, firmware_api_interface);
|
||||
size_t start = furi_get_tick();
|
||||
|
||||
FURI_LOG_I(TAG, "Loading %s", path);
|
||||
|
||||
FlipperApplicationPreloadStatus preload_res =
|
||||
flipper_application_preload(loader->app.fap, path);
|
||||
if(preload_res != FlipperApplicationPreloadStatusSuccess) {
|
||||
const char* err_msg = flipper_application_preload_status_to_string(preload_res);
|
||||
status = loader_make_status_error(
|
||||
LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg);
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Mapping");
|
||||
FlipperApplicationLoadStatus load_status =
|
||||
flipper_application_map_to_memory(loader->app.fap);
|
||||
if(load_status != FlipperApplicationLoadStatusSuccess) {
|
||||
const char* err_msg = flipper_application_load_status_to_string(load_status);
|
||||
status = loader_make_status_error(
|
||||
LoaderStatusErrorInternal, error_message, "Load failed %s: %s", path, err_msg);
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start));
|
||||
FURI_LOG_I(TAG, "Starting app");
|
||||
|
||||
loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args);
|
||||
FuriString* app_name = furi_string_alloc();
|
||||
path_extract_filename_no_ext(path, app_name);
|
||||
furi_thread_set_appid(loader->app.thread, furi_string_get_cstr(app_name));
|
||||
furi_string_free(app_name);
|
||||
|
||||
/* This flag is set by the debugger - to break on app start */
|
||||
if(furi_hal_debug_is_gdb_session_active()) {
|
||||
FURI_LOG_W(TAG, "Triggering BP for debugger");
|
||||
/* After hitting this, you can set breakpoints in your .fap's code
|
||||
* Note that you have to toggle breakpoints that were set before */
|
||||
__asm volatile("bkpt 0");
|
||||
}
|
||||
|
||||
loader_start_app_thread(loader, FlipperInternalApplicationFlagDefault);
|
||||
} while(0);
|
||||
|
||||
if(status != LoaderStatusOk) {
|
||||
flipper_application_free(loader->app.fap);
|
||||
loader->app.fap = NULL;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// process messages
|
||||
|
||||
static void loader_do_menu_show(Loader* loader) {
|
||||
if(!loader->loader_menu) {
|
||||
loader->loader_menu = loader_menu_alloc();
|
||||
loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader);
|
||||
loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader);
|
||||
loader_menu_start(loader->loader_menu);
|
||||
loader->loader_menu = loader_menu_alloc(loader_menu_closed_callback, loader);
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_do_menu_closed(Loader* loader) {
|
||||
if(loader->loader_menu) {
|
||||
loader_menu_stop(loader->loader_menu);
|
||||
loader_menu_free(loader->loader_menu);
|
||||
loader->loader_menu = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_do_applications_show(Loader* loader) {
|
||||
if(!loader->loader_applications) {
|
||||
loader->loader_applications =
|
||||
loader_applications_alloc(loader_applications_closed_callback, loader);
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_do_applications_closed(Loader* loader) {
|
||||
if(loader->loader_applications) {
|
||||
loader_applications_free(loader->loader_applications);
|
||||
loader->loader_applications = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool loader_do_is_locked(Loader* loader) {
|
||||
return loader->app.thread != NULL;
|
||||
}
|
||||
|
||||
static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) {
|
||||
if(loader_do_is_locked(loader)) {
|
||||
return LoaderStatusErrorAppStarted;
|
||||
}
|
||||
static LoaderStatus loader_do_start_by_name(
|
||||
Loader* loader,
|
||||
const char* name,
|
||||
const char* args,
|
||||
FuriString* error_message) {
|
||||
LoaderStatus status;
|
||||
do {
|
||||
// check lock
|
||||
if(loader_do_is_locked(loader)) {
|
||||
const char* current_thread_name =
|
||||
furi_thread_get_name(furi_thread_get_id(loader->app.thread));
|
||||
status = loader_make_status_error(
|
||||
LoaderStatusErrorAppStarted,
|
||||
error_message,
|
||||
"Loader is locked, please close the \"%s\" first",
|
||||
current_thread_name);
|
||||
break;
|
||||
}
|
||||
|
||||
const FlipperApplication* app = loader_find_application_by_name(name);
|
||||
// check internal apps
|
||||
{
|
||||
const FlipperInternalApplication* app = loader_find_application_by_name(name);
|
||||
if(app) {
|
||||
loader_start_internal_app(loader, app, args);
|
||||
status = loader_make_success_status(error_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!app) {
|
||||
return LoaderStatusErrorUnknownApp;
|
||||
}
|
||||
// check Applications
|
||||
if(strcmp(name, LOADER_APPLICATIONS_NAME) == 0) {
|
||||
loader_do_applications_show(loader);
|
||||
status = loader_make_success_status(error_message);
|
||||
break;
|
||||
}
|
||||
|
||||
loader_start_internal_app(loader, app, args);
|
||||
return LoaderStatusOk;
|
||||
// check external apps
|
||||
{
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_file_exists(storage, name)) {
|
||||
status = loader_start_external_app(loader, storage, name, args, error_message);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
break;
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
status = loader_make_status_error(
|
||||
LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name);
|
||||
} while(false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool loader_do_lock(Loader* loader) {
|
||||
@@ -229,13 +407,16 @@ static bool loader_do_lock(Loader* loader) {
|
||||
}
|
||||
|
||||
static void loader_do_unlock(Loader* loader) {
|
||||
furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE);
|
||||
furi_check(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE);
|
||||
loader->app.thread = NULL;
|
||||
}
|
||||
|
||||
static void loader_do_app_closed(Loader* loader) {
|
||||
furi_assert(loader->app.thread);
|
||||
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
|
||||
|
||||
furi_thread_join(loader->app.thread);
|
||||
FURI_LOG_I(TAG, "App returned: %li", furi_thread_get_return_code(loader->app.thread));
|
||||
|
||||
if(loader->app.args) {
|
||||
free(loader->app.args);
|
||||
loader->app.args = NULL;
|
||||
@@ -245,12 +426,20 @@ static void loader_do_app_closed(Loader* loader) {
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
|
||||
free(loader->app.name);
|
||||
loader->app.name = NULL;
|
||||
if(loader->app.fap) {
|
||||
flipper_application_free(loader->app.fap);
|
||||
loader->app.fap = NULL;
|
||||
loader->app.thread = NULL;
|
||||
} else {
|
||||
furi_thread_free(loader->app.thread);
|
||||
loader->app.thread = NULL;
|
||||
}
|
||||
|
||||
furi_thread_join(loader->app.thread);
|
||||
furi_thread_free(loader->app.thread);
|
||||
loader->app.thread = NULL;
|
||||
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
|
||||
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationStopped;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
}
|
||||
|
||||
// app
|
||||
@@ -266,7 +455,7 @@ int32_t loader_srv(void* p) {
|
||||
}
|
||||
|
||||
if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) {
|
||||
loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL);
|
||||
loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL, NULL);
|
||||
}
|
||||
|
||||
LoaderMessage message;
|
||||
@@ -274,8 +463,8 @@ int32_t loader_srv(void* p) {
|
||||
if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
|
||||
switch(message.type) {
|
||||
case LoaderMessageTypeStartByName:
|
||||
message.status_value->value =
|
||||
loader_do_start_by_name(loader, message.start.name, message.start.args);
|
||||
message.status_value->value = loader_do_start_by_name(
|
||||
loader, message.start.name, message.start.args, message.start.error_message);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeShowMenu:
|
||||
@@ -297,6 +486,10 @@ int32_t loader_srv(void* p) {
|
||||
break;
|
||||
case LoaderMessageTypeUnlock:
|
||||
loader_do_unlock(loader);
|
||||
break;
|
||||
case LoaderMessageTypeApplicationsClosed:
|
||||
loader_do_applications_closed(loader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user