mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +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",
|
entry_point="accessor_app",
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=4 * 1024,
|
stack_size=4 * 1024,
|
||||||
order=40,
|
|
||||||
fap_category="Debug",
|
fap_category="Debug",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
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",
|
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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
32
applications/services/loader/loader_queue.c
Normal file
32
applications/services/loader/loader_queue.c
Normal 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;
|
||||||
|
}
|
||||||
53
applications/services/loader/loader_queue.h
Normal file
53
applications/services/loader/loader_queue.h
Normal 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);
|
||||||
@@ -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*
|
||||||
|
|||||||
|
@@ -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*
|
||||||
|
|||||||
|
Reference in New Issue
Block a user