mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 04:41:26 +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:
@@ -6,6 +6,5 @@ App(
|
||||
entry_point="accessor_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=40,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ App(
|
||||
"power",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="blink_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -13,6 +13,5 @@ App(
|
||||
"bt_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=110,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -10,6 +10,5 @@ App(
|
||||
"ccid_test",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="direct_draw_app",
|
||||
requires=["gui", "input"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
requires=["gui"],
|
||||
fap_libs=["u8g2"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="event_loop_blink_test_app",
|
||||
requires=["input"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
requires=["expansion_start"],
|
||||
fap_libs=["assets"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
fap_file_assets="assets",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="file_browser_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=150,
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="keypad_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=30,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -11,6 +11,5 @@ App(
|
||||
"lfrfid_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=100,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
8
applications/debug/loader_chaining_a/application.fam
Normal file
8
applications/debug/loader_chaining_a/application.fam
Normal 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",
|
||||
)
|
||||
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal 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;
|
||||
}
|
||||
8
applications/debug/loader_chaining_b/application.fam
Normal file
8
applications/debug/loader_chaining_b/application.fam
Normal 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",
|
||||
)
|
||||
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal 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;
|
||||
}
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="locale_test_app",
|
||||
requires=["gui", "locale"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="rpc_debug_app",
|
||||
requires=["gui", "rpc_start", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="speaker_debug_app",
|
||||
requires=["gui", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
fap_libs=["music_worker"],
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
entry_point="subghz_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=50,
|
||||
fap_icon="subghz_test_10px.png",
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="images",
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_element_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_view_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="uart_echo_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ App(
|
||||
requires=["system_settings", "cli_subghz"],
|
||||
provides=["delay_test"],
|
||||
resources="resources",
|
||||
order=100,
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_mouse_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=60,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="vibro_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user