1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 12:51:22 +04:00

[FL-3953] Application chaining (#4105)

* feat: app chaining

* add `launch_current_app_after_deferred`, remove `get_referring_application`

* fix naming

* new api

* fix f18

* fix deferred launches after errors

* fix: memory leak

* Updater: MIN_GAP_PAGES = 0

* loader: loader_get_application_launch_path doc

* loader: fix freeze

* loader: reject mlib, reduce code size

* loader: generic synchronous call, reduce size

* loader: reject furi_string, reduce size

* apps: debug: removed order field from manifests since it is no longer meaningful

---------

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
Anna Antonenko
2025-04-05 20:22:05 +04:00
committed by GitHub
parent dac1457f0a
commit 6b5d006690
37 changed files with 512 additions and 73 deletions

View File

@@ -6,6 +6,5 @@ App(
entry_point="accessor_app", entry_point="accessor_app",
requires=["gui"], requires=["gui"],
stack_size=4 * 1024, stack_size=4 * 1024,
order=40,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -8,7 +8,6 @@ App(
"power", "power",
], ],
stack_size=1 * 1024, stack_size=1 * 1024,
order=130,
fap_category="Debug", fap_category="Debug",
fap_libs=["assets"], fap_libs=["assets"],
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="blink_test_app", entry_point="blink_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=10,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -13,6 +13,5 @@ App(
"bt_debug", "bt_debug",
], ],
stack_size=1 * 1024, stack_size=1 * 1024,
order=110,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -10,6 +10,5 @@ App(
"ccid_test", "ccid_test",
], ],
stack_size=1 * 1024, stack_size=1 * 1024,
order=120,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="direct_draw_app", entry_point="direct_draw_app",
requires=["gui", "input"], requires=["gui", "input"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=70,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -6,6 +6,5 @@ App(
requires=["gui"], requires=["gui"],
fap_libs=["u8g2"], fap_libs=["u8g2"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=120,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="event_loop_blink_test_app", entry_point="event_loop_blink_test_app",
requires=["input"], requires=["input"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=20,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -6,7 +6,6 @@ App(
requires=["expansion_start"], requires=["expansion_start"],
fap_libs=["assets"], fap_libs=["assets"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=20,
fap_category="Debug", fap_category="Debug",
fap_file_assets="assets", fap_file_assets="assets",
) )

View File

@@ -5,7 +5,6 @@ App(
entry_point="file_browser_app", entry_point="file_browser_app",
requires=["gui"], requires=["gui"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=150,
fap_category="Debug", fap_category="Debug",
fap_icon_assets="icons", fap_icon_assets="icons",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="keypad_test_app", entry_point="keypad_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=30,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -11,6 +11,5 @@ App(
"lfrfid_debug", "lfrfid_debug",
], ],
stack_size=1 * 1024, stack_size=1 * 1024,
order=100,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -0,0 +1,8 @@
App(
appid="loader_chaining_a",
name="Loader Chaining Test: App A",
apptype=FlipperAppType.DEBUG,
entry_point="chaining_test_app_a",
stack_size=1 * 1024,
fap_category="Debug",
)

View File

@@ -0,0 +1,164 @@
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <loader/loader.h>
#include <dialogs/dialogs.h>
#define TAG "LoaderChainingA"
#define CHAINING_TEST_B "/ext/apps/Debug/loader_chaining_b.fap"
#define NONEXISTENT_APP "Some nonexistent app"
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
Loader* loader;
DialogsApp* dialogs;
} LoaderChainingA;
typedef enum {
LoaderChainingASubmenuLaunchB,
LoaderChainingASubmenuLaunchBThenA,
LoaderChainingASubmenuLaunchNonexistentSilent,
LoaderChainingASubmenuLaunchNonexistentGui,
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
} LoaderChainingASubmenu;
static void loader_chaining_a_submenu_callback(void* context, uint32_t index) {
LoaderChainingA* app = context;
FuriString* self_path = furi_string_alloc();
furi_check(loader_get_application_launch_path(app->loader, self_path));
switch(index) {
case LoaderChainingASubmenuLaunchB:
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
view_dispatcher_stop(app->view_dispatcher);
break;
case LoaderChainingASubmenuLaunchBThenA:
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
loader_enqueue_launch(
app->loader,
furi_string_get_cstr(self_path),
"Hello to you from the future",
LoaderDeferredLaunchFlagGui);
break;
case LoaderChainingASubmenuLaunchNonexistentSilent:
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagNone);
break;
case LoaderChainingASubmenuLaunchNonexistentGui:
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
break;
case LoaderChainingASubmenuLaunchNonexistentGuiThenA:
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
loader_enqueue_launch(
app->loader,
furi_string_get_cstr(self_path),
"Hello to you from the future",
LoaderDeferredLaunchFlagGui);
break;
}
furi_string_free(self_path);
view_dispatcher_stop(app->view_dispatcher);
}
static bool loader_chaining_a_nav_callback(void* context) {
LoaderChainingA* app = context;
view_dispatcher_stop(app->view_dispatcher);
return true;
}
LoaderChainingA* loader_chaining_a_alloc(void) {
LoaderChainingA* app = malloc(sizeof(LoaderChainingA));
app->gui = furi_record_open(RECORD_GUI);
app->loader = furi_record_open(RECORD_LOADER);
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
app->submenu = submenu_alloc();
submenu_add_item(
app->submenu,
"Launch B",
LoaderChainingASubmenuLaunchB,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Launch B, then A",
LoaderChainingASubmenuLaunchBThenA,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Trigger error: silent",
LoaderChainingASubmenuLaunchNonexistentSilent,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Trigger error: GUI",
LoaderChainingASubmenuLaunchNonexistentGui,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Error, then launch A",
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
loader_chaining_a_submenu_callback,
app);
view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu));
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, loader_chaining_a_nav_callback);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
return app;
}
void loader_chaining_a_free(LoaderChainingA* app) {
furi_record_close(RECORD_DIALOGS);
furi_record_close(RECORD_LOADER);
furi_record_close(RECORD_GUI);
view_dispatcher_remove_view(app->view_dispatcher, 0);
submenu_free(app->submenu);
view_dispatcher_free(app->view_dispatcher);
free(app);
}
int32_t chaining_test_app_a(const char* arg) {
LoaderChainingA* app = loader_chaining_a_alloc();
if(arg) {
if(strlen(arg)) {
DialogMessage* message = dialog_message_alloc();
FuriString* text;
dialog_message_set_header(message, "Hi, I am A", 64, 0, AlignCenter, AlignTop);
text = furi_string_alloc_printf("Me from the past says:\n%s", arg);
dialog_message_set_buttons(message, NULL, "ok!", NULL);
dialog_message_set_text(
message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
dialog_message_show(app->dialogs, message);
dialog_message_free(message);
furi_string_free(text);
}
}
view_dispatcher_run(app->view_dispatcher);
loader_chaining_a_free(app);
return 0;
}

View File

@@ -0,0 +1,8 @@
App(
appid="loader_chaining_b",
name="Loader Chaining Test: App B",
apptype=FlipperAppType.DEBUG,
entry_point="chaining_test_app_b",
stack_size=1 * 1024,
fap_category="Debug",
)

View File

@@ -0,0 +1,27 @@
#include <furi.h>
#include <dialogs/dialogs.h>
#include <loader/loader.h>
int32_t chaining_test_app_b(const char* arg) {
if(!arg) return 0;
Loader* loader = furi_record_open(RECORD_LOADER);
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Hi, I am B", 64, 0, AlignCenter, AlignTop);
FuriString* text = furi_string_alloc_printf("And A told me:\n%s", arg);
dialog_message_set_text(message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
dialog_message_set_buttons(message, "Just quit", NULL, "Launch A");
DialogMessageButton result = dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_string_free(text);
if(result == DialogMessageButtonRight)
loader_enqueue_launch(
loader, "/ext/apps/Debug/loader_chaining_a.fap", NULL, LoaderDeferredLaunchFlagGui);
furi_record_close(RECORD_LOADER);
furi_record_close(RECORD_DIALOGS);
return 0;
}

View File

@@ -5,6 +5,5 @@ App(
entry_point="locale_test_app", entry_point="locale_test_app",
requires=["gui", "locale"], requires=["gui", "locale"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=70,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="rpc_debug_app", entry_point="rpc_debug_app",
requires=["gui", "rpc_start", "notification"], requires=["gui", "rpc_start", "notification"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=10,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,7 +5,6 @@ App(
entry_point="speaker_debug_app", entry_point="speaker_debug_app",
requires=["gui", "notification"], requires=["gui", "notification"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=10,
fap_category="Debug", fap_category="Debug",
fap_libs=["music_worker"], fap_libs=["music_worker"],
) )

View File

@@ -6,7 +6,6 @@ App(
entry_point="subghz_test_app", entry_point="subghz_test_app",
requires=["gui"], requires=["gui"],
stack_size=4 * 1024, stack_size=4 * 1024,
order=50,
fap_icon="subghz_test_10px.png", fap_icon="subghz_test_10px.png",
fap_category="Debug", fap_category="Debug",
fap_icon_assets="images", fap_icon_assets="images",

View File

@@ -5,6 +5,5 @@ App(
entry_point="text_box_element_test_app", entry_point="text_box_element_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=140,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="text_box_view_test_app", entry_point="text_box_view_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=140,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="uart_echo_app", entry_point="uart_echo_app",
requires=["gui"], requires=["gui"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=70,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -7,7 +7,6 @@ App(
requires=["system_settings", "cli_subghz"], requires=["system_settings", "cli_subghz"],
provides=["delay_test"], provides=["delay_test"],
resources="resources", resources="resources",
order=100,
) )
App( App(

View File

@@ -5,6 +5,5 @@ App(
entry_point="usb_mouse_app", entry_point="usb_mouse_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=60,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="usb_test_app", entry_point="usb_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=50,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -5,6 +5,5 @@ App(
entry_point="vibro_test_app", entry_point="vibro_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=20,
fap_category="Debug", fap_category="Debug",
) )

View File

@@ -42,7 +42,7 @@ static void archive_loader_callback(const void* message, void* context) {
const LoaderEvent* event = message; const LoaderEvent* event = message;
ArchiveApp* archive = (ArchiveApp*)context; ArchiveApp* archive = (ArchiveApp*)context;
if(event->type == LoaderEventTypeApplicationStopped) { if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
view_dispatcher_send_custom_event( view_dispatcher_send_custom_event(
archive->view_dispatcher, ArchiveBrowserEventListRefresh); archive->view_dispatcher, ArchiveBrowserEventListRefresh);
} }

View File

@@ -27,9 +27,7 @@ static void desktop_loader_callback(const void* message, void* context) {
if(event->type == LoaderEventTypeApplicationBeforeLoad) { if(event->type == LoaderEventTypeApplicationBeforeLoad) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted);
furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk);
} else if( } else if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
event->type == LoaderEventTypeApplicationLoadFailed ||
event->type == LoaderEventTypeApplicationStopped) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished);
} }
} }

View File

@@ -167,6 +167,13 @@ static void loader_show_gui_error(
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
} }
static void loader_generic_synchronous_request(Loader* loader, LoaderMessage* message) {
furi_check(loader);
message->api_lock = api_lock_alloc_locked();
furi_message_queue_put(loader->queue, message, FuriWaitForever);
api_lock_wait_unlock_and_free(message->api_lock);
}
LoaderStatus LoaderStatus
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
furi_check(loader); furi_check(loader);
@@ -202,16 +209,12 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons
} }
bool loader_lock(Loader* loader) { bool loader_lock(Loader* loader) {
furi_check(loader);
LoaderMessage message;
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
message.type = LoaderMessageTypeLock; LoaderMessage message = {
message.api_lock = api_lock_alloc_locked(); .type = LoaderMessageTypeLock,
message.bool_value = &result; .bool_value = &result,
furi_message_queue_put(loader->queue, &message, FuriWaitForever); };
api_lock_wait_unlock_and_free(message.api_lock); loader_generic_synchronous_request(loader, &message);
return result.value; return result.value;
} }
@@ -225,16 +228,12 @@ void loader_unlock(Loader* loader) {
} }
bool loader_is_locked(Loader* loader) { bool loader_is_locked(Loader* loader) {
furi_check(loader);
LoaderMessage message;
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
message.type = LoaderMessageTypeIsLocked; LoaderMessage message = {
message.api_lock = api_lock_alloc_locked(); .type = LoaderMessageTypeIsLocked,
message.bool_value = &result; .bool_value = &result,
furi_message_queue_put(loader->queue, &message, FuriWaitForever); };
api_lock_wait_unlock_and_free(message.api_lock); loader_generic_synchronous_request(loader, &message);
return result.value; return result.value;
} }
@@ -256,42 +255,63 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
} }
bool loader_signal(Loader* loader, uint32_t signal, void* arg) { bool loader_signal(Loader* loader, uint32_t signal, void* arg) {
furi_check(loader);
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
LoaderMessage message = { LoaderMessage message = {
.type = LoaderMessageTypeSignal, .type = LoaderMessageTypeSignal,
.api_lock = api_lock_alloc_locked(),
.signal.signal = signal, .signal.signal = signal,
.signal.arg = arg, .signal.arg = arg,
.bool_value = &result, .bool_value = &result,
}; };
loader_generic_synchronous_request(loader, &message);
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
return result.value; return result.value;
} }
bool loader_get_application_name(Loader* loader, FuriString* name) { bool loader_get_application_name(Loader* loader, FuriString* name) {
furi_check(loader);
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
LoaderMessage message = { LoaderMessage message = {
.type = LoaderMessageTypeGetApplicationName, .type = LoaderMessageTypeGetApplicationName,
.api_lock = api_lock_alloc_locked(),
.application_name = name, .application_name = name,
.bool_value = &result, .bool_value = &result,
}; };
loader_generic_synchronous_request(loader, &message);
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
return result.value; return result.value;
} }
bool loader_get_application_launch_path(Loader* loader, FuriString* name) {
LoaderMessageBoolResult result;
LoaderMessage message = {
.type = LoaderMessageTypeGetApplicationLaunchPath,
.application_name = name,
.bool_value = &result,
};
loader_generic_synchronous_request(loader, &message);
return result.value;
}
void loader_enqueue_launch(
Loader* loader,
const char* name,
const char* args,
LoaderDeferredLaunchFlag flags) {
LoaderMessage message = {
.type = LoaderMessageTypeEnqueueLaunch,
.defer_start =
{
.name_or_path = strdup(name),
.args = args ? strdup(args) : NULL,
.flags = flags,
},
};
loader_generic_synchronous_request(loader, &message);
}
void loader_clear_launch_queue(Loader* loader) {
LoaderMessage message = {
.type = LoaderMessageTypeClearLaunchQueue,
};
loader_generic_synchronous_request(loader, &message);
}
// callbacks // callbacks
static void loader_menu_closed_callback(void* context) { static void loader_menu_closed_callback(void* context) {
@@ -328,12 +348,10 @@ static Loader* loader_alloc(void) {
Loader* loader = malloc(sizeof(Loader)); Loader* loader = malloc(sizeof(Loader));
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->gui = furi_record_open(RECORD_GUI);
loader->loader_applications = NULL; loader->view_holder = view_holder_alloc();
loader->app.args = NULL; loader->loading = loading_alloc();
loader->app.thread = NULL; view_holder_attach_to_gui(loader->view_holder, loader->gui);
loader->app.insomniac = false;
loader->app.fap = NULL;
return loader; return loader;
} }
@@ -656,6 +674,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name); LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name);
} while(false); } while(false);
if(status.value == LoaderStatusOk) {
loader->app.launch_path = furi_string_alloc_set_str(name);
}
return status; return status;
} }
@@ -673,6 +695,57 @@ static void loader_do_unlock(Loader* loader) {
loader->app.thread = NULL; loader->app.thread = NULL;
} }
static void loader_do_emit_queue_empty_event(Loader* loader) {
FURI_LOG_I(TAG, "Launch queue empty");
LoaderEvent event;
event.type = LoaderEventTypeNoMoreAppsInQueue;
furi_pubsub_publish(loader->pubsub, &event);
}
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record);
static void loader_do_next_deferred_launch_if_available(Loader* loader) {
LoaderDeferredLaunchRecord record;
if(loader_queue_pop(&loader->launch_queue, &record)) {
loader_do_deferred_launch(loader, &record);
loader_queue_item_clear(&record);
} else {
loader_do_emit_queue_empty_event(loader);
}
}
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record) {
furi_assert(loader);
furi_assert(record);
bool is_successful = false;
FuriString* error_message = furi_string_alloc();
view_holder_set_view(loader->view_holder, loading_get_view(loader->loading));
view_holder_send_to_front(loader->view_holder);
do {
const char* app_name_str = record->name_or_path;
const char* app_args = record->args;
FURI_LOG_I(TAG, "Deferred launch: %s", app_name_str);
LoaderMessageLoaderStatusResult result =
loader_do_start_by_name(loader, app_name_str, app_args, error_message);
if(result.value == LoaderStatusOk) {
is_successful = true;
break;
}
if(record->flags & LoaderDeferredLaunchFlagGui)
loader_show_gui_error(result, app_name_str, error_message);
loader_do_next_deferred_launch_if_available(loader);
} while(false);
view_holder_set_view(loader->view_holder, NULL);
furi_string_free(error_message);
return is_successful;
}
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);
@@ -697,11 +770,15 @@ static void loader_do_app_closed(Loader* loader) {
loader->app.thread = NULL; loader->app.thread = NULL;
} }
furi_string_free(loader->app.launch_path);
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
LoaderEvent event; LoaderEvent event;
event.type = LoaderEventTypeApplicationStopped; event.type = LoaderEventTypeApplicationStopped;
furi_pubsub_publish(loader->pubsub, &event); furi_pubsub_publish(loader->pubsub, &event);
loader_do_next_deferred_launch_if_available(loader);
} }
static bool loader_is_application_running(Loader* loader) { static bool loader_is_application_running(Loader* loader) {
@@ -726,6 +803,15 @@ static bool loader_do_get_application_name(Loader* loader, FuriString* name) {
return false; return false;
} }
static bool loader_do_get_application_launch_path(Loader* loader, FuriString* path) {
if(loader_is_application_running(loader)) {
furi_string_set(path, loader->app.launch_path);
return true;
}
return false;
}
// app // app
int32_t loader_srv(void* p) { int32_t loader_srv(void* p) {
@@ -748,16 +834,20 @@ int32_t loader_srv(void* p) {
while(true) { while(true) {
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) = loader_do_start_by_name( LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, message.start.error_message); loader, message.start.name, message.start.args, message.start.error_message);
*(message.status_value) = status;
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
api_lock_unlock(message.api_lock); api_lock_unlock(message.api_lock);
break; break;
}
case LoaderMessageTypeStartByNameDetachedWithGuiError: { case LoaderMessageTypeStartByNameDetachedWithGuiError: {
FuriString* error_message = furi_string_alloc(); FuriString* error_message = furi_string_alloc();
LoaderMessageLoaderStatusResult status = loader_do_start_by_name( LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, error_message); loader, message.start.name, message.start.args, error_message);
loader_show_gui_error(status, message.start.name, error_message); loader_show_gui_error(status, message.start.name, error_message);
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
if(message.start.name) free((void*)message.start.name); if(message.start.name) free((void*)message.start.name);
if(message.start.args) free((void*)message.start.args); if(message.start.args) free((void*)message.start.args);
furi_string_free(error_message); furi_string_free(error_message);
@@ -796,6 +886,19 @@ int32_t loader_srv(void* p) {
loader_do_get_application_name(loader, message.application_name); loader_do_get_application_name(loader, message.application_name);
api_lock_unlock(message.api_lock); api_lock_unlock(message.api_lock);
break; break;
case LoaderMessageTypeGetApplicationLaunchPath:
message.bool_value->value =
loader_do_get_application_launch_path(loader, message.application_name);
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeEnqueueLaunch:
furi_check(loader_queue_push(&loader->launch_queue, &message.defer_start));
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeClearLaunchQueue:
loader_queue_clear(&loader->launch_queue);
api_lock_unlock(message.api_lock);
break;
} }
} }
} }

View File

@@ -20,13 +20,19 @@ typedef enum {
typedef enum { typedef enum {
LoaderEventTypeApplicationBeforeLoad, LoaderEventTypeApplicationBeforeLoad,
LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationLoadFailed,
LoaderEventTypeApplicationStopped LoaderEventTypeApplicationStopped,
LoaderEventTypeNoMoreAppsInQueue, //<! The normal `Stopped` event still fires before this one
} LoaderEventType; } LoaderEventType;
typedef struct { typedef struct {
LoaderEventType type; LoaderEventType type;
} LoaderEvent; } LoaderEvent;
typedef enum {
LoaderDeferredLaunchFlagNone = 0,
LoaderDeferredLaunchFlagGui = (1 << 1), //<! Report launch to the user via a dialog
} LoaderDeferredLaunchFlag;
/** /**
* @brief Start application * @brief Start application
* @param[in] instance loader instance * @param[in] instance loader instance
@@ -93,7 +99,7 @@ FuriPubSub* loader_get_pubsub(Loader* instance);
* *
* @param[in] instance pointer to the loader instance * @param[in] instance pointer to the loader instance
* @param[in] signal signal value to be sent * @param[in] signal signal value to be sent
* @param[in,out] arg optional argument (can be of any value, including NULL) * @param[inout] arg optional argument (can be of any value, including NULL)
* *
* @return true if the signal was handled by the application, false otherwise * @return true if the signal was handled by the application, false otherwise
*/ */
@@ -103,11 +109,49 @@ bool loader_signal(Loader* instance, uint32_t signal, void* arg);
* @brief Get the name of the currently running application * @brief Get the name of the currently running application
* *
* @param[in] instance pointer to the loader instance * @param[in] instance pointer to the loader instance
* @param[in,out] name pointer to the string to contain the name (must be allocated) * @param[inout] name pointer to the string to contain the name (must be allocated)
* @return true if it was possible to get an application name, false otherwise * @return true if it was possible to get an application name, false otherwise
*/ */
bool loader_get_application_name(Loader* instance, FuriString* name); bool loader_get_application_name(Loader* instance, FuriString* name);
/**
* @brief Get the launch path or name of the currently running application
*
* This is the string that was supplied to `loader_start` such that the current
* app is running now. It might be a name (in the case of internal apps) or a
* path (in the case of external apps). This value can be used to launch the
* same app again.
*
* @param[in] instance pointer to the loader instance
* @param[inout] name pointer to the string to contain the path or name
* (must be allocated)
* @return true if it was possible to get an application path, false otherwise
*/
bool loader_get_application_launch_path(Loader* instance, FuriString* name);
/**
* @brief Enqueues a request to launch an application after the current one
*
* @param[in] instance pointer to the loader instance
* @param[in] name pointer to the name or path of the application, or NULL to
* cancel a previous request
* @param[in] args pointer to argument to provide to the next app
* @param[in] flags additional flags. see enum documentation for more info
*/
void loader_enqueue_launch(
Loader* instance,
const char* name,
const char* args,
LoaderDeferredLaunchFlag flags);
/**
* @brief Removes all requests to launch applications after the current one
* exits
*
* @param[in] instance pointer to the loader instance
*/
void loader_clear_launch_queue(Loader* instance);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -120,7 +120,7 @@ static void loader_pubsub_callback(const void* message, void* context) {
const LoaderEvent* event = message; const LoaderEvent* event = message;
const FuriThreadId thread_id = (FuriThreadId)context; const FuriThreadId thread_id = (FuriThreadId)context;
if(event->type == LoaderEventTypeApplicationStopped) { if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT); furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT);
} }
} }

View File

@@ -2,11 +2,20 @@
#include <furi.h> #include <furi.h>
#include <toolbox/api_lock.h> #include <toolbox/api_lock.h>
#include <flipper_application/flipper_application.h> #include <flipper_application/flipper_application.h>
#include <gui/gui.h>
#include <gui/view_holder.h>
#include <gui/modules/loading.h>
#include <m-array.h>
#include "loader.h" #include "loader.h"
#include "loader_menu.h" #include "loader_menu.h"
#include "loader_applications.h" #include "loader_applications.h"
#include "loader_queue.h"
typedef struct { typedef struct {
FuriString* launch_path;
char* args; char* args;
FuriThread* thread; FuriThread* thread;
bool insomniac; bool insomniac;
@@ -19,6 +28,12 @@ struct Loader {
LoaderMenu* loader_menu; LoaderMenu* loader_menu;
LoaderApplications* loader_applications; LoaderApplications* loader_applications;
LoaderAppData app; LoaderAppData app;
LoaderLaunchQueue launch_queue;
Gui* gui;
ViewHolder* view_holder;
Loading* loading;
}; };
typedef enum { typedef enum {
@@ -33,6 +48,9 @@ typedef enum {
LoaderMessageTypeStartByNameDetachedWithGuiError, LoaderMessageTypeStartByNameDetachedWithGuiError,
LoaderMessageTypeSignal, LoaderMessageTypeSignal,
LoaderMessageTypeGetApplicationName, LoaderMessageTypeGetApplicationName,
LoaderMessageTypeGetApplicationLaunchPath,
LoaderMessageTypeEnqueueLaunch,
LoaderMessageTypeClearLaunchQueue,
} LoaderMessageType; } LoaderMessageType;
typedef struct { typedef struct {
@@ -72,6 +90,7 @@ typedef struct {
union { union {
LoaderMessageStartByName start; LoaderMessageStartByName start;
LoaderDeferredLaunchRecord defer_start;
LoaderMessageSignal signal; LoaderMessageSignal signal;
FuriString* application_name; FuriString* application_name;
}; };

View File

@@ -0,0 +1,32 @@
#include "loader_queue.h"
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item) {
free(item->args);
free(item->name_or_path);
}
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
if(!queue->item_cnt) return false;
*item = queue->items[0];
queue->item_cnt--;
memmove(
&queue->items[0], &queue->items[1], queue->item_cnt * sizeof(LoaderDeferredLaunchRecord));
return true;
}
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
if(queue->item_cnt == LOADER_QUEUE_MAX_SIZE) return false;
queue->items[queue->item_cnt] = *item;
queue->item_cnt++;
return true;
}
void loader_queue_clear(LoaderLaunchQueue* queue) {
for(size_t i = 0; i < queue->item_cnt; i++)
loader_queue_item_clear(&queue->items[i]);
queue->item_cnt = 0;
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <furi.h>
#include "loader.h"
#define LOADER_QUEUE_MAX_SIZE 4
typedef struct {
char* name_or_path;
char* args;
LoaderDeferredLaunchFlag flags;
} LoaderDeferredLaunchRecord;
typedef struct {
LoaderDeferredLaunchRecord items[LOADER_QUEUE_MAX_SIZE];
size_t item_cnt;
} LoaderLaunchQueue;
/**
* @brief Frees internal data in a `DeferredLaunchRecord`
*
* @param[out] item Record to clear
*/
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item);
/**
* @brief Fetches the next item from the launch queue
*
* @param[inout] queue Queue instance
* @param[out] item Item output
*
* @return `true` if `item` was populated, `false` if queue is empty
*/
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
/**
* @brief Puts an item into the launch queue
*
* @param[inout] queue Queue instance
* @param[in] item Item to put in the queue
*
* @return `true` if the item was put into the queue, `false` if there's no more
* space left
*/
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
/**
* @brief Clears the launch queue
*
* @param[inout] queue Queue instance
*/
void loader_queue_clear(LoaderLaunchQueue* queue);

View File

@@ -1860,6 +1860,9 @@ Function,-,llrintl,long long int,long double
Function,-,llround,long long int,double Function,-,llround,long long int,double
Function,-,llroundf,long long int,float Function,-,llroundf,long long int,float
Function,-,llroundl,long long int,long double Function,-,llroundl,long long int,long double
Function,+,loader_clear_launch_queue,void,Loader*
Function,+,loader_enqueue_launch,void,"Loader*, const char*, const char*, LoaderDeferredLaunchFlag"
Function,+,loader_get_application_launch_path,_Bool,"Loader*, FuriString*"
Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*"
Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_get_pubsub,FuriPubSub*,Loader*
Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_is_locked,_Bool,Loader*
1 entry status name type params
1860 Function - llround long long int double
1861 Function - llroundf long long int float
1862 Function - llroundl long long int long double
1863 Function + loader_clear_launch_queue void Loader*
1864 Function + loader_enqueue_launch void Loader*, const char*, const char*, LoaderDeferredLaunchFlag
1865 Function + loader_get_application_launch_path _Bool Loader*, FuriString*
1866 Function + loader_get_application_name _Bool Loader*, FuriString*
1867 Function + loader_get_pubsub FuriPubSub* Loader*
1868 Function + loader_is_locked _Bool Loader*

View File

@@ -2288,6 +2288,9 @@ Function,-,llrintl,long long int,long double
Function,-,llround,long long int,double Function,-,llround,long long int,double
Function,-,llroundf,long long int,float Function,-,llroundf,long long int,float
Function,-,llroundl,long long int,long double Function,-,llroundl,long long int,long double
Function,+,loader_clear_launch_queue,void,Loader*
Function,+,loader_enqueue_launch,void,"Loader*, const char*, const char*, LoaderDeferredLaunchFlag"
Function,+,loader_get_application_launch_path,_Bool,"Loader*, FuriString*"
Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*"
Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_get_pubsub,FuriPubSub*,Loader*
Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_is_locked,_Bool,Loader*
1 entry status name type params
2288 Function - llround long long int double
2289 Function - llroundf long long int float
2290 Function - llroundl long long int long double
2291 Function + loader_clear_launch_queue void Loader*
2292 Function + loader_enqueue_launch void Loader*, const char*, const char*, LoaderDeferredLaunchFlag
2293 Function + loader_get_application_launch_path _Bool Loader*, FuriString*
2294 Function + loader_get_application_name _Bool Loader*, FuriString*
2295 Function + loader_get_pubsub FuriPubSub* Loader*
2296 Function + loader_is_locked _Bool Loader*