فهرست منبع

[FL-3270] Loader refactoring, part 1 (#2593)

* Loader: menu part
* Settings: remove unused loader api
* Desktop: get loader from record_open
* CLI: remove unneeded loader api
* gitignore: ignore .old files
* Loader: now really a service
* Loader: working service prototype
* Loader: cli, system start hooks
* CI/CD: make happy
* Loader: autorun
* Loader: lock and unlock
* Loader: rearrange code
* Gui, module menu: fix memleak
* Updater test: add timeout
* added update timeouts and max run duration
* Github: revert updater test workflow changes
* Loader: less missleading message in info cli command

Co-authored-by: doomwastaken <k.volkov@flipperdevices.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Sergey Gavrilov 2 سال پیش
والد
کامیت
a7d1ec03e8

+ 1 - 0
.gitignore

@@ -2,6 +2,7 @@
 *.swp
 *.swo
 *.gdb_history
+*.old
 
 
 # LSP

+ 0 - 2
applications/services/cli/cli_commands.c

@@ -220,11 +220,9 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) {
     UNUSED(context);
     if(!furi_string_cmp(args, "0")) {
         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
-        loader_update_menu();
         printf("Debug disabled.");
     } else if(!furi_string_cmp(args, "1")) {
         furi_hal_rtc_set_flag(FuriHalRtcFlagDebug);
-        loader_update_menu();
         printf("Debug enabled.");
     } else {
         cli_print_usage("sysctl debug", "<1|0>", furi_string_get_cstr(args));

+ 5 - 3
applications/services/desktop/scenes/desktop_scene_main.c

@@ -106,10 +106,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         switch(event.event) {
-        case DesktopMainEventOpenMenu:
-            loader_show_menu();
+        case DesktopMainEventOpenMenu: {
+            Loader* loader = furi_record_open(RECORD_LOADER);
+            loader_show_menu(loader);
+            furi_record_close(RECORD_LOADER);
             consumed = true;
-            break;
+        } break;
 
         case DesktopMainEventOpenLockMenu:
             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu);

+ 2 - 0
applications/services/gui/modules/menu.c

@@ -154,6 +154,8 @@ Menu* menu_alloc() {
 void menu_free(Menu* menu) {
     furi_assert(menu);
     menu_reset(menu);
+    with_view_model(
+        menu->view, MenuModel * model, { MenuItemArray_clear(model->items); }, false);
     view_free(menu->view);
     free(menu);
 }

+ 9 - 0
applications/services/loader/application.fam

@@ -5,6 +5,7 @@ App(
     entry_point="loader_srv",
     cdefines=["SRV_LOADER"],
     requires=["gui"],
+    provides=["loader_start"],
     stack_size=2 * 1024,
     order=90,
     sdk_headers=[
@@ -12,3 +13,11 @@ App(
         "firmware_api/firmware_api.h",
     ],
 )
+
+App(
+    appid="loader_start",
+    apptype=FlipperAppType.STARTUP,
+    entry_point="loader_on_system_start",
+    requires=["loader"],
+    order=90,
+)

+ 206 - 347
applications/services/loader/loader.c

@@ -1,76 +1,114 @@
-#include "applications.h"
-#include <furi.h>
-#include "loader/loader.h"
+#include "loader.h"
 #include "loader_i.h"
+#include "loader_menu.h"
+#include <applications.h>
+#include <furi_hal.h>
+
+#define TAG "Loader"
+#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF
+// api
+
+LoaderStatus loader_start(Loader* loader, const char* name, const char* args) {
+    LoaderMessage message;
+    LoaderMessageLoaderStatusResult result;
+
+    message.type = LoaderMessageTypeStartByName;
+    message.start.name = name;
+    message.start.args = args;
+    message.api_lock = api_lock_alloc_locked();
+    message.status_value = &result;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+    api_lock_wait_unlock_and_free(message.api_lock);
+    return result.value;
+}
 
-#define TAG "LoaderSrv"
-
-#define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0)
-#define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU)
-
-static Loader* loader_instance = NULL;
+bool loader_lock(Loader* loader) {
+    LoaderMessage message;
+    LoaderMessageBoolResult result;
+    message.type = LoaderMessageTypeLock;
+    message.api_lock = api_lock_alloc_locked();
+    message.bool_value = &result;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+    api_lock_wait_unlock_and_free(message.api_lock);
+    return result.value;
+}
 
-static bool
-    loader_start_application(const FlipperApplication* application, const char* arguments) {
-    loader_instance->application = application;
+void loader_unlock(Loader* loader) {
+    LoaderMessage message;
+    message.type = LoaderMessageTypeUnlock;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+}
 
-    furi_assert(loader_instance->application_arguments == NULL);
-    if(arguments && strlen(arguments) > 0) {
-        loader_instance->application_arguments = strdup(arguments);
-    }
+bool loader_is_locked(Loader* loader) {
+    LoaderMessage message;
+    LoaderMessageBoolResult result;
+    message.type = LoaderMessageTypeIsLocked;
+    message.api_lock = api_lock_alloc_locked();
+    message.bool_value = &result;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+    api_lock_wait_unlock_and_free(message.api_lock);
+    return result.value;
+}
 
-    FURI_LOG_I(TAG, "Starting: %s", loader_instance->application->name);
+void loader_show_menu(Loader* loader) {
+    LoaderMessage message;
+    message.type = LoaderMessageTypeShowMenu;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+}
 
-    FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
-    if(mode > FuriHalRtcHeapTrackModeNone) {
-        furi_thread_enable_heap_trace(loader_instance->application_thread);
-    } else {
-        furi_thread_disable_heap_trace(loader_instance->application_thread);
-    }
+FuriPubSub* loader_get_pubsub(Loader* loader) {
+    furi_assert(loader);
+    // it's safe to return pubsub without locking
+    // because it's never freed and loader is never exited
+    // also the loader instance cannot be obtained until the pubsub is created
+    return loader->pubsub;
+}
 
-    furi_thread_set_name(loader_instance->application_thread, loader_instance->application->name);
-    furi_thread_set_appid(
-        loader_instance->application_thread, loader_instance->application->appid);
-    furi_thread_set_stack_size(
-        loader_instance->application_thread, loader_instance->application->stack_size);
-    furi_thread_set_context(
-        loader_instance->application_thread, loader_instance->application_arguments);
-    furi_thread_set_callback(
-        loader_instance->application_thread, loader_instance->application->app);
+// callbacks
 
-    furi_thread_start(loader_instance->application_thread);
+static void loader_menu_closed_callback(void* context) {
+    Loader* loader = context;
+    LoaderMessage message;
+    message.type = LoaderMessageTypeMenuClosed;
+    furi_message_queue_put(loader->queue, &message, FuriWaitForever);
+}
 
-    return true;
+static void loader_menu_click_callback(const char* name, void* context) {
+    Loader* loader = context;
+    loader_start(loader, name, NULL);
 }
 
-static void loader_menu_callback(void* _ctx, uint32_t index) {
-    UNUSED(index);
-    const FlipperApplication* application = _ctx;
+static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
+    furi_assert(context);
 
-    furi_assert(application->app);
-    furi_assert(application->name);
+    Loader* loader = context;
+    LoaderEvent event;
 
-    if(!loader_lock(loader_instance)) {
-        FURI_LOG_E(TAG, "Loader is locked");
-        return;
-    }
+    if(thread_state == FuriThreadStateRunning) {
+        event.type = LoaderEventTypeApplicationStarted;
+        furi_pubsub_publish(loader->pubsub, &event);
+    } else if(thread_state == FuriThreadStateStopped) {
+        LoaderMessage message;
+        message.type = LoaderMessageTypeAppClosed;
+        furi_message_queue_put(loader->queue, &message, FuriWaitForever);
 
-    loader_start_application(application, NULL);
+        event.type = LoaderEventTypeApplicationStopped;
+        furi_pubsub_publish(loader->pubsub, &event);
+    }
 }
 
-static void loader_submenu_callback(void* context, uint32_t index) {
-    UNUSED(index);
-    uint32_t view_id = (uint32_t)context;
-    view_dispatcher_switch_to_view(loader_instance->view_dispatcher, view_id);
-}
+// implementation
 
-static void loader_cli_print_usage() {
-    printf("Usage:\r\n");
-    printf("loader <cmd> <args>\r\n");
-    printf("Cmd list:\r\n");
-    printf("\tlist\t - List available applications\r\n");
-    printf("\topen <Application Name:string>\t - Open application by name\r\n");
-    printf("\tinfo\t - Show loader state\r\n");
+static Loader* loader_alloc() {
+    Loader* loader = malloc(sizeof(Loader));
+    loader->pubsub = furi_pubsub_alloc();
+    loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
+    loader->loader_menu = NULL;
+    loader->app.args = NULL;
+    loader->app.name = NULL;
+    loader->app.thread = NULL;
+    loader->app.insomniac = false;
+    return loader;
 }
 
 static FlipperApplication const* loader_find_application_by_name_in_list(
@@ -85,7 +123,7 @@ static FlipperApplication const* loader_find_application_by_name_in_list(
     return NULL;
 }
 
-const FlipperApplication* loader_find_application_by_name(const char* name) {
+static const FlipperApplication* loader_find_application_by_name(const char* name) {
     const FlipperApplication* application = NULL;
     application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT);
     if(!application) {
@@ -100,346 +138,167 @@ const FlipperApplication* loader_find_application_by_name(const char* name) {
     return application;
 }
 
-static void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) {
-    UNUSED(cli);
-    if(loader_is_locked(instance)) {
-        if(instance->application) {
-            furi_assert(instance->application->name);
-            printf("Can't start, %s application is running", instance->application->name);
-        } else {
-            printf("Can't start, furi application is running");
-        }
-        return;
-    }
-
-    FuriString* application_name;
-    application_name = furi_string_alloc();
-
-    do {
-        if(!args_read_probably_quoted_string_and_trim(args, application_name)) {
-            printf("No application provided\r\n");
-            break;
-        }
+static void
+    loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) {
+    FURI_LOG_I(TAG, "Starting %s", app->name);
 
-        const FlipperApplication* application =
-            loader_find_application_by_name(furi_string_get_cstr(application_name));
-        if(!application) {
-            printf("%s doesn't exists\r\n", furi_string_get_cstr(application_name));
-            break;
-        }
+    // store args
+    furi_assert(loader->app.args == NULL);
+    if(args && strlen(args) > 0) {
+        loader->app.args = strdup(args);
+    }
 
-        furi_string_trim(args);
-        if(!loader_start_application(application, furi_string_get_cstr(args))) {
-            printf("Can't start, furi application is running");
-            return;
-        } else {
-            // We must to increment lock counter to keep balance
-            // TODO: rewrite whole thing, it's complex as hell
-            FURI_CRITICAL_ENTER();
-            instance->lock_count++;
-            FURI_CRITICAL_EXIT();
-        }
-    } while(false);
+    // store name
+    furi_assert(loader->app.name == NULL);
+    loader->app.name = strdup(app->name);
 
-    furi_string_free(application_name);
-}
+    // setup app thread
+    loader->app.thread =
+        furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args);
+    furi_thread_set_appid(loader->app.thread, app->appid);
 
-static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) {
-    UNUSED(cli);
-    UNUSED(args);
-    UNUSED(instance);
-    printf("Applications:\r\n");
-    for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
-        printf("\t%s\r\n", FLIPPER_APPS[i].name);
+    // setup heap trace
+    FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
+    if(mode > FuriHalRtcHeapTrackModeNone) {
+        furi_thread_enable_heap_trace(loader->app.thread);
+    } else {
+        furi_thread_disable_heap_trace(loader->app.thread);
     }
-}
 
-static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) {
-    UNUSED(cli);
-    UNUSED(args);
-    if(!loader_is_locked(instance)) {
-        printf("No application is running\r\n");
+    // setup insomnia
+    if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) {
+        furi_hal_power_insomnia_enter();
+        loader->app.insomniac = true;
     } else {
-        printf("Running application: ");
-        if(instance->application) {
-            furi_assert(instance->application->name);
-            printf("%s\r\n", instance->application->name);
-        } else {
-            printf("unknown\r\n");
-        }
+        loader->app.insomniac = false;
     }
-}
 
-static void loader_cli(Cli* cli, FuriString* args, void* _ctx) {
-    furi_assert(_ctx);
-    Loader* instance = _ctx;
+    // setup app thread callbacks
+    furi_thread_set_state_context(loader->app.thread, loader);
+    furi_thread_set_state_callback(loader->app.thread, loader_thread_state_callback);
 
-    FuriString* cmd;
-    cmd = furi_string_alloc();
-
-    do {
-        if(!args_read_string_and_trim(args, cmd)) {
-            loader_cli_print_usage();
-            break;
-        }
-
-        if(furi_string_cmp_str(cmd, "list") == 0) {
-            loader_cli_list(cli, args, instance);
-            break;
-        }
-
-        if(furi_string_cmp_str(cmd, "open") == 0) {
-            loader_cli_open(cli, args, instance);
-            break;
-        }
-
-        if(furi_string_cmp_str(cmd, "info") == 0) {
-            loader_cli_info(cli, args, instance);
-            break;
-        }
-
-        loader_cli_print_usage();
-    } while(false);
-
-    furi_string_free(cmd);
+    // start app thread
+    furi_thread_start(loader->app.thread);
 }
 
-LoaderStatus loader_start(Loader* instance, const char* name, const char* args) {
-    UNUSED(instance);
-    furi_assert(name);
+// process messages
 
-    const FlipperApplication* application = loader_find_application_by_name(name);
-
-    if(!application) {
-        FURI_LOG_E(TAG, "Can't find application with name %s", name);
-        return LoaderStatusErrorUnknownApp;
+static void loader_do_menu_show(Loader* loader) {
+    if(!loader->loader_menu) {
+        loader->loader_menu = loader_menu_alloc();
+        loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader);
+        loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader);
+        loader_menu_start(loader->loader_menu);
     }
-
-    if(!loader_lock(loader_instance)) {
-        FURI_LOG_E(TAG, "Loader is locked");
-        return LoaderStatusErrorAppStarted;
-    }
-
-    if(!loader_start_application(application, args)) {
-        return LoaderStatusErrorInternal;
-    }
-
-    return LoaderStatusOk;
 }
 
-bool loader_lock(Loader* instance) {
-    FURI_CRITICAL_ENTER();
-    bool result = false;
-    if(instance->lock_count == 0) {
-        instance->lock_count++;
-        result = true;
+static void loader_do_menu_closed(Loader* loader) {
+    if(loader->loader_menu) {
+        loader_menu_stop(loader->loader_menu);
+        loader_menu_free(loader->loader_menu);
+        loader->loader_menu = NULL;
     }
-    FURI_CRITICAL_EXIT();
-    return result;
 }
 
-void loader_unlock(Loader* instance) {
-    FURI_CRITICAL_ENTER();
-    if(instance->lock_count > 0) instance->lock_count--;
-    FURI_CRITICAL_EXIT();
+static bool loader_do_is_locked(Loader* loader) {
+    return loader->app.thread != NULL;
 }
 
-bool loader_is_locked(const Loader* instance) {
-    return instance->lock_count > 0;
-}
-
-static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
-    furi_assert(context);
-
-    Loader* instance = context;
-    LoaderEvent event;
-
-    if(thread_state == FuriThreadStateRunning) {
-        event.type = LoaderEventTypeApplicationStarted;
-        furi_pubsub_publish(loader_instance->pubsub, &event);
-
-        if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) {
-            furi_hal_power_insomnia_enter();
-        }
-    } else if(thread_state == FuriThreadStateStopped) {
-        FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
-
-        if(loader_instance->application_arguments) {
-            free(loader_instance->application_arguments);
-            loader_instance->application_arguments = NULL;
-        }
-
-        if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) {
-            furi_hal_power_insomnia_exit();
-        }
-        loader_unlock(instance);
-
-        event.type = LoaderEventTypeApplicationStopped;
-        furi_pubsub_publish(loader_instance->pubsub, &event);
+static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) {
+    if(loader_do_is_locked(loader)) {
+        return LoaderStatusErrorAppStarted;
     }
-}
 
-static uint32_t loader_hide_menu(void* context) {
-    UNUSED(context);
-    return VIEW_NONE;
-}
+    const FlipperApplication* app = loader_find_application_by_name(name);
 
-static uint32_t loader_back_to_primary_menu(void* context) {
-    furi_assert(context);
-    Submenu* submenu = context;
-    submenu_set_selected_item(submenu, 0);
-    return LoaderMenuViewPrimary;
-}
+    if(!app) {
+        return LoaderStatusErrorUnknownApp;
+    }
 
-static Loader* loader_alloc() {
-    Loader* instance = malloc(sizeof(Loader));
-
-    instance->application_thread = furi_thread_alloc();
-
-    furi_thread_set_state_context(instance->application_thread, instance);
-    furi_thread_set_state_callback(instance->application_thread, loader_thread_state_callback);
-
-    instance->pubsub = furi_pubsub_alloc();
-
-#ifdef SRV_CLI
-    instance->cli = furi_record_open(RECORD_CLI);
-    cli_add_command(
-        instance->cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, instance);
-#else
-    UNUSED(loader_cli);
-#endif
-
-    instance->loader_thread = furi_thread_get_current_id();
-
-    // Gui
-    instance->gui = furi_record_open(RECORD_GUI);
-    instance->view_dispatcher = view_dispatcher_alloc();
-    view_dispatcher_attach_to_gui(
-        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
-    // Primary menu
-    instance->primary_menu = menu_alloc();
-    view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu);
-    view_dispatcher_add_view(
-        instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu));
-    // Settings menu
-    instance->settings_menu = submenu_alloc();
-    view_set_context(submenu_get_view(instance->settings_menu), instance->settings_menu);
-    view_set_previous_callback(
-        submenu_get_view(instance->settings_menu), loader_back_to_primary_menu);
-    view_dispatcher_add_view(
-        instance->view_dispatcher,
-        LoaderMenuViewSettings,
-        submenu_get_view(instance->settings_menu));
-
-    view_dispatcher_enable_queue(instance->view_dispatcher);
-
-    return instance;
+    loader_start_internal_app(loader, app, args);
+    return LoaderStatusOk;
 }
 
-static void loader_free(Loader* instance) {
-    furi_assert(instance);
-
-    if(instance->cli) {
-        furi_record_close(RECORD_CLI);
+static bool loader_do_lock(Loader* loader) {
+    if(loader->app.thread) {
+        return false;
     }
 
-    furi_pubsub_free(instance->pubsub);
-
-    furi_thread_free(instance->application_thread);
-
-    menu_free(loader_instance->primary_menu);
-    view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary);
-    submenu_free(loader_instance->settings_menu);
-    view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings);
-    view_dispatcher_free(loader_instance->view_dispatcher);
-
-    furi_record_close(RECORD_GUI);
+    loader->app.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE;
+    return true;
+}
 
-    free(instance);
-    instance = NULL;
+static void loader_do_unlock(Loader* loader) {
+    furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE);
+    loader->app.thread = NULL;
 }
 
-static void loader_build_menu() {
-    FURI_LOG_I(TAG, "Building main menu");
-    size_t i;
-    for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
-        menu_add_item(
-            loader_instance->primary_menu,
-            FLIPPER_APPS[i].name,
-            FLIPPER_APPS[i].icon,
-            i,
-            loader_menu_callback,
-            (void*)&FLIPPER_APPS[i]);
+static void loader_do_app_closed(Loader* loader) {
+    furi_assert(loader->app.thread);
+    FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
+    if(loader->app.args) {
+        free(loader->app.args);
+        loader->app.args = NULL;
     }
-    menu_add_item(
-        loader_instance->primary_menu,
-        "Settings",
-        &A_Settings_14,
-        i++,
-        loader_submenu_callback,
-        (void*)LoaderMenuViewSettings);
-}
 
-static void loader_build_submenu() {
-    FURI_LOG_I(TAG, "Building settings menu");
-    for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
-        submenu_add_item(
-            loader_instance->settings_menu,
-            FLIPPER_SETTINGS_APPS[i].name,
-            i,
-            loader_menu_callback,
-            (void*)&FLIPPER_SETTINGS_APPS[i]);
+    if(loader->app.insomniac) {
+        furi_hal_power_insomnia_exit();
     }
-}
 
-void loader_show_menu() {
-    furi_assert(loader_instance);
-    furi_thread_flags_set(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU);
-}
+    free(loader->app.name);
+    loader->app.name = NULL;
 
-void loader_update_menu() {
-    menu_reset(loader_instance->primary_menu);
-    loader_build_menu();
+    furi_thread_free(loader->app.thread);
+    loader->app.thread = NULL;
 }
 
+// app
+
 int32_t loader_srv(void* p) {
     UNUSED(p);
+    Loader* loader = loader_alloc();
+    furi_record_create(RECORD_LOADER, loader);
+
     FURI_LOG_I(TAG, "Executing system start hooks");
     for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) {
         FLIPPER_ON_SYSTEM_START[i]();
     }
 
-    FURI_LOG_I(TAG, "Starting");
-    loader_instance = loader_alloc();
-
-    loader_build_menu();
-    loader_build_submenu();
-
-    FURI_LOG_I(TAG, "Started");
-
-    furi_record_create(RECORD_LOADER, loader_instance);
-
     if(FLIPPER_AUTORUN_APP_NAME && strlen(FLIPPER_AUTORUN_APP_NAME)) {
-        loader_start(loader_instance, FLIPPER_AUTORUN_APP_NAME, NULL);
+        loader_do_start_by_name(loader, FLIPPER_AUTORUN_APP_NAME, NULL);
     }
 
-    while(1) {
-        uint32_t flags =
-            furi_thread_flags_wait(LOADER_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
-        if(flags & LOADER_THREAD_FLAG_SHOW_MENU) {
-            menu_set_selected_item(loader_instance->primary_menu, 0);
-            view_dispatcher_switch_to_view(
-                loader_instance->view_dispatcher, LoaderMenuViewPrimary);
-            view_dispatcher_run(loader_instance->view_dispatcher);
+    LoaderMessage message;
+    while(true) {
+        if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
+            switch(message.type) {
+            case LoaderMessageTypeStartByName:
+                message.status_value->value =
+                    loader_do_start_by_name(loader, message.start.name, message.start.args);
+                api_lock_unlock(message.api_lock);
+                break;
+            case LoaderMessageTypeShowMenu:
+                loader_do_menu_show(loader);
+                break;
+            case LoaderMessageTypeMenuClosed:
+                loader_do_menu_closed(loader);
+                break;
+            case LoaderMessageTypeIsLocked:
+                message.bool_value->value = loader_do_is_locked(loader);
+                api_lock_unlock(message.api_lock);
+                break;
+            case LoaderMessageTypeAppClosed:
+                loader_do_app_closed(loader);
+                break;
+            case LoaderMessageTypeLock:
+                message.bool_value->value = loader_do_lock(loader);
+                api_lock_unlock(message.api_lock);
+                break;
+            case LoaderMessageTypeUnlock:
+                loader_do_unlock(loader);
+            }
         }
     }
 
-    furi_record_destroy(RECORD_LOADER);
-    loader_free(loader_instance);
-
     return 0;
-}
-
-FuriPubSub* loader_get_pubsub(Loader* instance) {
-    return instance->pubsub;
-}
+}

+ 4 - 9
applications/services/loader/loader.h

@@ -1,7 +1,5 @@
 #pragma once
-
-#include <core/pubsub.h>
-#include <stdbool.h>
+#include <furi.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -43,17 +41,14 @@ bool loader_lock(Loader* instance);
 void loader_unlock(Loader* instance);
 
 /** Get loader lock status */
-bool loader_is_locked(const Loader* instance);
-
-/** Show primary loader */
-void loader_show_menu();
+bool loader_is_locked(Loader* instance);
 
 /** Show primary loader */
-void loader_update_menu();
+void loader_show_menu(Loader* instance);
 
 /** Show primary loader */
 FuriPubSub* loader_get_pubsub(Loader* instance);
 
 #ifdef __cplusplus
 }
-#endif
+#endif

+ 117 - 0
applications/services/loader/loader_cli.c

@@ -0,0 +1,117 @@
+#include <furi.h>
+#include <cli/cli.h>
+#include <applications.h>
+#include <lib/toolbox/args.h>
+#include "loader.h"
+
+static void loader_cli_print_usage() {
+    printf("Usage:\r\n");
+    printf("loader <cmd> <args>\r\n");
+    printf("Cmd list:\r\n");
+    printf("\tlist\t - List available applications\r\n");
+    printf("\topen <Application Name:string>\t - Open application by name\r\n");
+    printf("\tinfo\t - Show loader state\r\n");
+}
+
+static void loader_cli_list() {
+    printf("Applications:\r\n");
+    for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
+        printf("\t%s\r\n", FLIPPER_APPS[i].name);
+    }
+    printf("Settings:\r\n");
+    for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
+        printf("\t%s\r\n", FLIPPER_SETTINGS_APPS[i].name);
+    }
+}
+
+static void loader_cli_info(Loader* loader) {
+    if(!loader_is_locked(loader)) {
+        printf("No application is running\r\n");
+    } else {
+        // TODO: print application name ???
+        printf("Application is running\r\n");
+    }
+}
+
+static void loader_cli_open(FuriString* args, Loader* loader) {
+    FuriString* app_name = furi_string_alloc();
+
+    do {
+        if(!args_read_probably_quoted_string_and_trim(args, app_name)) {
+            printf("No application provided\r\n");
+            break;
+        }
+        furi_string_trim(args);
+
+        const char* args_str = furi_string_get_cstr(args);
+        if(strlen(args_str) == 0) {
+            args_str = NULL;
+        }
+
+        const char* app_name_str = furi_string_get_cstr(app_name);
+
+        LoaderStatus status = loader_start(loader, app_name_str, args_str);
+
+        switch(status) {
+        case LoaderStatusOk:
+            break;
+        case LoaderStatusErrorAppStarted:
+            printf("Can't start, application is running");
+            break;
+        case LoaderStatusErrorUnknownApp:
+            printf("%s doesn't exists\r\n", app_name_str);
+            break;
+        case LoaderStatusErrorInternal:
+            printf("Internal error\r\n");
+            break;
+        }
+    } while(false);
+
+    furi_string_free(app_name);
+}
+
+static void loader_cli(Cli* cli, FuriString* args, void* context) {
+    UNUSED(cli);
+    UNUSED(context);
+    Loader* loader = furi_record_open(RECORD_LOADER);
+
+    FuriString* cmd;
+    cmd = furi_string_alloc();
+
+    do {
+        if(!args_read_string_and_trim(args, cmd)) {
+            loader_cli_print_usage();
+            break;
+        }
+
+        if(furi_string_cmp_str(cmd, "list") == 0) {
+            loader_cli_list();
+            break;
+        }
+
+        if(furi_string_cmp_str(cmd, "open") == 0) {
+            loader_cli_open(args, loader);
+            break;
+        }
+
+        if(furi_string_cmp_str(cmd, "info") == 0) {
+            loader_cli_info(loader);
+            break;
+        }
+
+        loader_cli_print_usage();
+    } while(false);
+
+    furi_string_free(cmd);
+    furi_record_close(RECORD_LOADER);
+}
+
+void loader_on_system_start() {
+#ifdef SRV_CLI
+    Cli* cli = furi_record_open(RECORD_CLI);
+    cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
+    furi_record_close(RECORD_CLI);
+#else
+    UNUSED(loader_cli);
+#endif
+}

+ 48 - 31
applications/services/loader/loader_i.h

@@ -1,39 +1,56 @@
-#include "loader.h"
-
+#pragma once
 #include <furi.h>
-#include <furi_hal.h>
-#include <core/pubsub.h>
-#include <cli/cli.h>
-#include <lib/toolbox/args.h>
-
-#include <gui/view_dispatcher.h>
-
-#include <gui/modules/menu.h>
-#include <gui/modules/submenu.h>
+#include <toolbox/api_lock.h>
+#include "loader.h"
+#include "loader_menu.h"
 
-#include <applications.h>
-#include <assets_icons.h>
+typedef struct {
+    char* args;
+    char* name;
+    FuriThread* thread;
+    bool insomniac;
+} LoaderAppData;
 
 struct Loader {
-    FuriThreadId loader_thread;
-
-    const FlipperApplication* application;
-    FuriThread* application_thread;
-    char* application_arguments;
-
-    Cli* cli;
-    Gui* gui;
-
-    ViewDispatcher* view_dispatcher;
-    Menu* primary_menu;
-    Submenu* settings_menu;
-
-    volatile uint8_t lock_count;
-
     FuriPubSub* pubsub;
+    FuriMessageQueue* queue;
+    LoaderMenu* loader_menu;
+    LoaderAppData app;
 };
 
 typedef enum {
-    LoaderMenuViewPrimary,
-    LoaderMenuViewSettings,
-} LoaderMenuView;
+    LoaderMessageTypeStartByName,
+    LoaderMessageTypeAppClosed,
+    LoaderMessageTypeShowMenu,
+    LoaderMessageTypeMenuClosed,
+    LoaderMessageTypeLock,
+    LoaderMessageTypeUnlock,
+    LoaderMessageTypeIsLocked,
+} LoaderMessageType;
+
+typedef struct {
+    const char* name;
+    const char* args;
+} LoaderMessageStartByName;
+
+typedef struct {
+    LoaderStatus value;
+} LoaderMessageLoaderStatusResult;
+
+typedef struct {
+    bool value;
+} LoaderMessageBoolResult;
+
+typedef struct {
+    FuriApiLock api_lock;
+    LoaderMessageType type;
+
+    union {
+        LoaderMessageStartByName start;
+    };
+
+    union {
+        LoaderMessageLoaderStatusResult* status_value;
+        LoaderMessageBoolResult* bool_value;
+    };
+} LoaderMessage;

+ 187 - 0
applications/services/loader/loader_menu.c

@@ -0,0 +1,187 @@
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/submenu.h>
+#include <assets_icons.h>
+#include <applications.h>
+
+#include "loader_menu.h"
+
+#define TAG "LoaderMenu"
+
+struct LoaderMenu {
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    Menu* primary_menu;
+    Submenu* settings_menu;
+
+    void (*closed_callback)(void*);
+    void* closed_callback_context;
+
+    void (*click_callback)(const char*, void*);
+    void* click_callback_context;
+
+    FuriThread* thread;
+};
+
+typedef enum {
+    LoaderMenuViewPrimary,
+    LoaderMenuViewSettings,
+} LoaderMenuView;
+
+static int32_t loader_menu_thread(void* p);
+
+LoaderMenu* loader_menu_alloc() {
+    LoaderMenu* loader_menu = malloc(sizeof(LoaderMenu));
+    loader_menu->gui = furi_record_open(RECORD_GUI);
+    loader_menu->view_dispatcher = view_dispatcher_alloc();
+    loader_menu->primary_menu = menu_alloc();
+    loader_menu->settings_menu = submenu_alloc();
+    loader_menu->thread = NULL;
+    return loader_menu;
+}
+
+void loader_menu_free(LoaderMenu* loader_menu) {
+    furi_assert(loader_menu);
+    // check if thread is running
+    furi_assert(!loader_menu->thread);
+
+    submenu_free(loader_menu->settings_menu);
+    menu_free(loader_menu->primary_menu);
+    view_dispatcher_free(loader_menu->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+    free(loader_menu);
+}
+
+void loader_menu_start(LoaderMenu* loader_menu) {
+    furi_assert(loader_menu);
+    furi_assert(!loader_menu->thread);
+    loader_menu->thread = furi_thread_alloc_ex(TAG, 1024, loader_menu_thread, loader_menu);
+    furi_thread_start(loader_menu->thread);
+}
+
+void loader_menu_stop(LoaderMenu* loader_menu) {
+    furi_assert(loader_menu);
+    furi_assert(loader_menu->thread);
+    view_dispatcher_stop(loader_menu->view_dispatcher);
+    furi_thread_join(loader_menu->thread);
+    furi_thread_free(loader_menu->thread);
+    loader_menu->thread = NULL;
+}
+
+void loader_menu_set_closed_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(void*),
+    void* context) {
+    loader_menu->closed_callback = callback;
+    loader_menu->closed_callback_context = context;
+}
+
+void loader_menu_set_click_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(const char*, void*),
+    void* context) {
+    loader_menu->click_callback = callback;
+    loader_menu->click_callback_context = context;
+}
+
+static void loader_menu_callback(void* context, uint32_t index) {
+    LoaderMenu* loader_menu = context;
+    const char* name = FLIPPER_APPS[index].name;
+    if(loader_menu->click_callback) {
+        loader_menu->click_callback(name, loader_menu->click_callback_context);
+    }
+}
+
+static void loader_menu_settings_menu_callback(void* context, uint32_t index) {
+    LoaderMenu* loader_menu = context;
+    const char* name = FLIPPER_SETTINGS_APPS[index].name;
+    if(loader_menu->click_callback) {
+        loader_menu->click_callback(name, loader_menu->click_callback_context);
+    }
+}
+
+static void loader_menu_switch_to_settings(void* context, uint32_t index) {
+    UNUSED(index);
+    LoaderMenu* loader_menu = context;
+    view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewSettings);
+}
+
+static uint32_t loader_menu_switch_to_primary(void* context) {
+    UNUSED(context);
+    return LoaderMenuViewPrimary;
+}
+
+static uint32_t loader_menu_exit(void* context) {
+    UNUSED(context);
+    return VIEW_NONE;
+}
+
+static void loader_menu_build_menu(LoaderMenu* loader_menu) {
+    size_t i;
+    for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
+        menu_add_item(
+            loader_menu->primary_menu,
+            FLIPPER_APPS[i].name,
+            FLIPPER_APPS[i].icon,
+            i,
+            loader_menu_callback,
+            (void*)loader_menu);
+    }
+    menu_add_item(
+        loader_menu->primary_menu,
+        "Settings",
+        &A_Settings_14,
+        i++,
+        loader_menu_switch_to_settings,
+        loader_menu);
+};
+
+static void loader_menu_build_submenu(LoaderMenu* loader_menu) {
+    for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
+        submenu_add_item(
+            loader_menu->settings_menu,
+            FLIPPER_SETTINGS_APPS[i].name,
+            i,
+            loader_menu_settings_menu_callback,
+            loader_menu);
+    }
+}
+
+static int32_t loader_menu_thread(void* p) {
+    LoaderMenu* loader_menu = p;
+    furi_assert(loader_menu);
+
+    loader_menu_build_menu(loader_menu);
+    loader_menu_build_submenu(loader_menu);
+
+    view_dispatcher_attach_to_gui(
+        loader_menu->view_dispatcher, loader_menu->gui, ViewDispatcherTypeFullscreen);
+
+    // Primary menu
+    View* primary_view = menu_get_view(loader_menu->primary_menu);
+    view_set_context(primary_view, loader_menu->primary_menu);
+    view_set_previous_callback(primary_view, loader_menu_exit);
+    view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary, primary_view);
+
+    // Settings menu
+    View* settings_view = submenu_get_view(loader_menu->settings_menu);
+    view_set_context(settings_view, loader_menu->settings_menu);
+    view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
+    view_dispatcher_add_view(loader_menu->view_dispatcher, LoaderMenuViewSettings, settings_view);
+
+    view_dispatcher_enable_queue(loader_menu->view_dispatcher);
+    view_dispatcher_switch_to_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary);
+
+    // run view dispatcher
+    view_dispatcher_run(loader_menu->view_dispatcher);
+
+    view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewPrimary);
+    view_dispatcher_remove_view(loader_menu->view_dispatcher, LoaderMenuViewSettings);
+
+    if(loader_menu->closed_callback) {
+        loader_menu->closed_callback(loader_menu->closed_callback_context);
+    }
+
+    return 0;
+}

+ 30 - 0
applications/services/loader/loader_menu.h

@@ -0,0 +1,30 @@
+#pragma once
+#include <furi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct LoaderMenu LoaderMenu;
+
+LoaderMenu* loader_menu_alloc();
+
+void loader_menu_free(LoaderMenu* loader_menu);
+
+void loader_menu_start(LoaderMenu* loader_menu);
+
+void loader_menu_stop(LoaderMenu* loader_menu);
+
+void loader_menu_set_closed_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(void*),
+    void* context);
+
+void loader_menu_set_click_callback(
+    LoaderMenu* loader_menu,
+    void (*callback)(const char*, void*),
+    void* context);
+
+#ifdef __cplusplus
+}
+#endif

+ 0 - 3
applications/settings/system/system_settings.c

@@ -43,7 +43,6 @@ static void debug_changed(VariableItem* item) {
     } else {
         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
     }
-    loader_update_menu();
 }
 
 const char* const heap_trace_mode_text[] = {
@@ -137,8 +136,6 @@ static void hand_orient_changed(VariableItem* item) {
     } else {
         furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient);
     }
-
-    loader_update_menu();
 }
 
 const char* const sleep_method[] = {

+ 3 - 4
firmware/targets/f18/api_symbols.csv

@@ -1,5 +1,5 @@
 entry,status,name,type,params
-Version,+,23.3,,
+Version,+,24.0,,
 Header,+,applications/services/bt/bt_service/bt.h,,
 Header,+,applications/services/cli/cli.h,,
 Header,+,applications/services/cli/cli_vcp.h,,
@@ -1375,12 +1375,11 @@ Function,-,ldiv,ldiv_t,"long, long"
 Function,-,llabs,long long,long long
 Function,-,lldiv,lldiv_t,"long long, long long"
 Function,+,loader_get_pubsub,FuriPubSub*,Loader*
-Function,+,loader_is_locked,_Bool,const Loader*
+Function,+,loader_is_locked,_Bool,Loader*
 Function,+,loader_lock,_Bool,Loader*
-Function,+,loader_show_menu,void,
+Function,+,loader_show_menu,void,Loader*
 Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*"
 Function,+,loader_unlock,void,Loader*
-Function,+,loader_update_menu,void,
 Function,+,loading_alloc,Loading*,
 Function,+,loading_free,void,Loading*
 Function,+,loading_get_view,View*,Loading*

+ 3 - 4
firmware/targets/f7/api_symbols.csv

@@ -1,5 +1,5 @@
 entry,status,name,type,params
-Version,+,23.3,,
+Version,+,24.0,,
 Header,+,applications/services/bt/bt_service/bt.h,,
 Header,+,applications/services/cli/cli.h,,
 Header,+,applications/services/cli/cli_vcp.h,,
@@ -1795,12 +1795,11 @@ Function,-,llround,long long int,double
 Function,-,llroundf,long long int,float
 Function,-,llroundl,long long int,long double
 Function,+,loader_get_pubsub,FuriPubSub*,Loader*
-Function,+,loader_is_locked,_Bool,const Loader*
+Function,+,loader_is_locked,_Bool,Loader*
 Function,+,loader_lock,_Bool,Loader*
-Function,+,loader_show_menu,void,
+Function,+,loader_show_menu,void,Loader*
 Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*"
 Function,+,loader_unlock,void,Loader*
-Function,+,loader_update_menu,void,
 Function,+,loading_alloc,Loading*,
 Function,+,loading_free,void,Loading*
 Function,+,loading_get_view,View*,Loading*