[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>
This commit is contained in:
		
							parent
							
								
									d2ca67d261
								
							
						
					
					
						commit
						a7d1ec03e8
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -2,6 +2,7 @@ | |||||||
| *.swp | *.swp | ||||||
| *.swo | *.swo | ||||||
| *.gdb_history | *.gdb_history | ||||||
|  | *.old | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # LSP | # LSP | ||||||
|  | |||||||
| @ -220,11 +220,9 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { | |||||||
|     UNUSED(context); |     UNUSED(context); | ||||||
|     if(!furi_string_cmp(args, "0")) { |     if(!furi_string_cmp(args, "0")) { | ||||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); |         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); | ||||||
|         loader_update_menu(); |  | ||||||
|         printf("Debug disabled."); |         printf("Debug disabled."); | ||||||
|     } else if(!furi_string_cmp(args, "1")) { |     } else if(!furi_string_cmp(args, "1")) { | ||||||
|         furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); |         furi_hal_rtc_set_flag(FuriHalRtcFlagDebug); | ||||||
|         loader_update_menu(); |  | ||||||
|         printf("Debug enabled."); |         printf("Debug enabled."); | ||||||
|     } else { |     } else { | ||||||
|         cli_print_usage("sysctl debug", "<1|0>", furi_string_get_cstr(args)); |         cli_print_usage("sysctl debug", "<1|0>", furi_string_get_cstr(args)); | ||||||
|  | |||||||
| @ -106,10 +106,12 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case DesktopMainEventOpenMenu: |         case DesktopMainEventOpenMenu: { | ||||||
|             loader_show_menu(); |             Loader* loader = furi_record_open(RECORD_LOADER); | ||||||
|  |             loader_show_menu(loader); | ||||||
|  |             furi_record_close(RECORD_LOADER); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |         } break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenLockMenu: |         case DesktopMainEventOpenLockMenu: | ||||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu); |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu); | ||||||
|  | |||||||
| @ -154,6 +154,8 @@ Menu* menu_alloc() { | |||||||
| void menu_free(Menu* menu) { | void menu_free(Menu* menu) { | ||||||
|     furi_assert(menu); |     furi_assert(menu); | ||||||
|     menu_reset(menu); |     menu_reset(menu); | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, MenuModel * model, { MenuItemArray_clear(model->items); }, false); | ||||||
|     view_free(menu->view); |     view_free(menu->view); | ||||||
|     free(menu); |     free(menu); | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ App( | |||||||
|     entry_point="loader_srv", |     entry_point="loader_srv", | ||||||
|     cdefines=["SRV_LOADER"], |     cdefines=["SRV_LOADER"], | ||||||
|     requires=["gui"], |     requires=["gui"], | ||||||
|  |     provides=["loader_start"], | ||||||
|     stack_size=2 * 1024, |     stack_size=2 * 1024, | ||||||
|     order=90, |     order=90, | ||||||
|     sdk_headers=[ |     sdk_headers=[ | ||||||
| @ -12,3 +13,11 @@ App( | |||||||
|         "firmware_api/firmware_api.h", |         "firmware_api/firmware_api.h", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  | 
 | ||||||
|  | App( | ||||||
|  |     appid="loader_start", | ||||||
|  |     apptype=FlipperAppType.STARTUP, | ||||||
|  |     entry_point="loader_on_system_start", | ||||||
|  |     requires=["loader"], | ||||||
|  |     order=90, | ||||||
|  | ) | ||||||
|  | |||||||
| @ -1,76 +1,114 @@ | |||||||
| #include "applications.h" | #include "loader.h" | ||||||
| #include <furi.h> |  | ||||||
| #include "loader/loader.h" |  | ||||||
| #include "loader_i.h" | #include "loader_i.h" | ||||||
|  | #include "loader_menu.h" | ||||||
|  | #include <applications.h> | ||||||
|  | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "LoaderSrv" | #define TAG "Loader" | ||||||
|  | #define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF | ||||||
|  | // api
 | ||||||
| 
 | 
 | ||||||
| #define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0) | LoaderStatus loader_start(Loader* loader, const char* name, const char* args) { | ||||||
| #define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU) |     LoaderMessage message; | ||||||
|  |     LoaderMessageLoaderStatusResult result; | ||||||
| 
 | 
 | ||||||
| static Loader* loader_instance = NULL; |     message.type = LoaderMessageTypeStartByName; | ||||||
| 
 |     message.start.name = name; | ||||||
| static bool |     message.start.args = args; | ||||||
|     loader_start_application(const FlipperApplication* application, const char* arguments) { |     message.api_lock = api_lock_alloc_locked(); | ||||||
|     loader_instance->application = application; |     message.status_value = &result; | ||||||
| 
 |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
|     furi_assert(loader_instance->application_arguments == NULL); |     api_lock_wait_unlock_and_free(message.api_lock); | ||||||
|     if(arguments && strlen(arguments) > 0) { |     return result.value; | ||||||
|         loader_instance->application_arguments = strdup(arguments); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_I(TAG, "Starting: %s", loader_instance->application->name); |  | ||||||
| 
 |  | ||||||
|     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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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); |  | ||||||
| 
 |  | ||||||
|     furi_thread_start(loader_instance->application_thread); |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_menu_callback(void* _ctx, uint32_t index) { | bool loader_lock(Loader* loader) { | ||||||
|     UNUSED(index); |     LoaderMessage message; | ||||||
|     const FlipperApplication* application = _ctx; |     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; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     furi_assert(application->app); | void loader_unlock(Loader* loader) { | ||||||
|     furi_assert(application->name); |     LoaderMessage message; | ||||||
|  |     message.type = LoaderMessageTypeUnlock; | ||||||
|  |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     if(!loader_lock(loader_instance)) { | bool loader_is_locked(Loader* loader) { | ||||||
|         FURI_LOG_E(TAG, "Loader is locked"); |     LoaderMessage message; | ||||||
|         return; |     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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void loader_show_menu(Loader* loader) { | ||||||
|  |     LoaderMessage message; | ||||||
|  |     message.type = LoaderMessageTypeShowMenu; | ||||||
|  |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // callbacks
 | ||||||
|  | 
 | ||||||
|  | static void loader_menu_closed_callback(void* context) { | ||||||
|  |     Loader* loader = context; | ||||||
|  |     LoaderMessage message; | ||||||
|  |     message.type = LoaderMessageTypeMenuClosed; | ||||||
|  |     furi_message_queue_put(loader->queue, &message, FuriWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_menu_click_callback(const char* name, void* context) { | ||||||
|  |     Loader* loader = context; | ||||||
|  |     loader_start(loader, name, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     Loader* loader = context; | ||||||
|  |     LoaderEvent event; | ||||||
|  | 
 | ||||||
|  |     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); | ||||||
|  | 
 | ||||||
|  |         event.type = LoaderEventTypeApplicationStopped; | ||||||
|  |         furi_pubsub_publish(loader->pubsub, &event); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     loader_start_application(application, NULL); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_submenu_callback(void* context, uint32_t index) { | // implementation
 | ||||||
|     UNUSED(index); |  | ||||||
|     uint32_t view_id = (uint32_t)context; |  | ||||||
|     view_dispatcher_switch_to_view(loader_instance->view_dispatcher, view_id); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| static void loader_cli_print_usage() { | static Loader* loader_alloc() { | ||||||
|     printf("Usage:\r\n"); |     Loader* loader = malloc(sizeof(Loader)); | ||||||
|     printf("loader <cmd> <args>\r\n"); |     loader->pubsub = furi_pubsub_alloc(); | ||||||
|     printf("Cmd list:\r\n"); |     loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); | ||||||
|     printf("\tlist\t - List available applications\r\n"); |     loader->loader_menu = NULL; | ||||||
|     printf("\topen <Application Name:string>\t - Open application by name\r\n"); |     loader->app.args = NULL; | ||||||
|     printf("\tinfo\t - Show loader state\r\n"); |     loader->app.name = NULL; | ||||||
|  |     loader->app.thread = NULL; | ||||||
|  |     loader->app.insomniac = false; | ||||||
|  |     return loader; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static FlipperApplication const* loader_find_application_by_name_in_list( | 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; |     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; |     const FlipperApplication* application = NULL; | ||||||
|     application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); |     application = loader_find_application_by_name_in_list(name, FLIPPER_APPS, FLIPPER_APPS_COUNT); | ||||||
|     if(!application) { |     if(!application) { | ||||||
| @ -100,346 +138,167 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { | |||||||
|     return application; |     return application; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_cli_open(Cli* cli, FuriString* args, Loader* instance) { | static void | ||||||
|     UNUSED(cli); |     loader_start_internal_app(Loader* loader, const FlipperApplication* app, const char* args) { | ||||||
|     if(loader_is_locked(instance)) { |     FURI_LOG_I(TAG, "Starting %s", app->name); | ||||||
|         if(instance->application) { | 
 | ||||||
|             furi_assert(instance->application->name); |     // store args
 | ||||||
|             printf("Can't start, %s application is running", instance->application->name); |     furi_assert(loader->app.args == NULL); | ||||||
|  |     if(args && strlen(args) > 0) { | ||||||
|  |         loader->app.args = strdup(args); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // store name
 | ||||||
|  |     furi_assert(loader->app.name == NULL); | ||||||
|  |     loader->app.name = strdup(app->name); | ||||||
|  | 
 | ||||||
|  |     // setup app thread
 | ||||||
|  |     loader->app.thread = | ||||||
|  |         furi_thread_alloc_ex(app->name, app->stack_size, app->app, loader->app.args); | ||||||
|  |     furi_thread_set_appid(loader->app.thread, app->appid); | ||||||
|  | 
 | ||||||
|  |     // setup heap trace
 | ||||||
|  |     FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); | ||||||
|  |     if(mode > FuriHalRtcHeapTrackModeNone) { | ||||||
|  |         furi_thread_enable_heap_trace(loader->app.thread); | ||||||
|     } else { |     } else { | ||||||
|             printf("Can't start, furi application is running"); |         furi_thread_disable_heap_trace(loader->app.thread); | ||||||
|         } |  | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FuriString* application_name; |     // setup insomnia
 | ||||||
|     application_name = furi_string_alloc(); |     if(!(app->flags & FlipperApplicationFlagInsomniaSafe)) { | ||||||
| 
 |         furi_hal_power_insomnia_enter(); | ||||||
|     do { |         loader->app.insomniac = true; | ||||||
|         if(!args_read_probably_quoted_string_and_trim(args, application_name)) { |  | ||||||
|             printf("No application provided\r\n"); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         furi_string_trim(args); |  | ||||||
|         if(!loader_start_application(application, furi_string_get_cstr(args))) { |  | ||||||
|             printf("Can't start, furi application is running"); |  | ||||||
|             return; |  | ||||||
|     } else { |     } else { | ||||||
|             // We must to increment lock counter to keep balance
 |         loader->app.insomniac = false; | ||||||
|             // TODO: rewrite whole thing, it's complex as hell
 |  | ||||||
|             FURI_CRITICAL_ENTER(); |  | ||||||
|             instance->lock_count++; |  | ||||||
|             FURI_CRITICAL_EXIT(); |  | ||||||
|     } |     } | ||||||
|     } while(false); |  | ||||||
| 
 | 
 | ||||||
|     furi_string_free(application_name); |     // 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); | ||||||
|  | 
 | ||||||
|  |     // start app thread
 | ||||||
|  |     furi_thread_start(loader->app.thread); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_cli_list(Cli* cli, FuriString* args, Loader* instance) { | // process messages
 | ||||||
|     UNUSED(cli); | 
 | ||||||
|     UNUSED(args); | static void loader_do_menu_show(Loader* loader) { | ||||||
|     UNUSED(instance); |     if(!loader->loader_menu) { | ||||||
|     printf("Applications:\r\n"); |         loader->loader_menu = loader_menu_alloc(); | ||||||
|     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { |         loader_menu_set_closed_callback(loader->loader_menu, loader_menu_closed_callback, loader); | ||||||
|         printf("\t%s\r\n", FLIPPER_APPS[i].name); |         loader_menu_set_click_callback(loader->loader_menu, loader_menu_click_callback, loader); | ||||||
|  |         loader_menu_start(loader->loader_menu); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_cli_info(Cli* cli, FuriString* args, Loader* instance) { | static void loader_do_menu_closed(Loader* loader) { | ||||||
|     UNUSED(cli); |     if(loader->loader_menu) { | ||||||
|     UNUSED(args); |         loader_menu_stop(loader->loader_menu); | ||||||
|     if(!loader_is_locked(instance)) { |         loader_menu_free(loader->loader_menu); | ||||||
|         printf("No application is running\r\n"); |         loader->loader_menu = NULL; | ||||||
|     } else { |  | ||||||
|         printf("Running application: "); |  | ||||||
|         if(instance->application) { |  | ||||||
|             furi_assert(instance->application->name); |  | ||||||
|             printf("%s\r\n", instance->application->name); |  | ||||||
|         } else { |  | ||||||
|             printf("unknown\r\n"); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_cli(Cli* cli, FuriString* args, void* _ctx) { | static bool loader_do_is_locked(Loader* loader) { | ||||||
|     furi_assert(_ctx); |     return loader->app.thread != NULL; | ||||||
|     Loader* instance = _ctx; |  | ||||||
| 
 |  | ||||||
|     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); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| LoaderStatus loader_start(Loader* instance, const char* name, const char* args) { | static LoaderStatus loader_do_start_by_name(Loader* loader, const char* name, const char* args) { | ||||||
|     UNUSED(instance); |     if(loader_do_is_locked(loader)) { | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!loader_lock(loader_instance)) { |  | ||||||
|         FURI_LOG_E(TAG, "Loader is locked"); |  | ||||||
|         return LoaderStatusErrorAppStarted; |         return LoaderStatusErrorAppStarted; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!loader_start_application(application, args)) { |     const FlipperApplication* app = loader_find_application_by_name(name); | ||||||
|         return LoaderStatusErrorInternal; | 
 | ||||||
|  |     if(!app) { | ||||||
|  |         return LoaderStatusErrorUnknownApp; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     loader_start_internal_app(loader, app, args); | ||||||
|     return LoaderStatusOk; |     return LoaderStatusOk; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool loader_lock(Loader* instance) { | static bool loader_do_lock(Loader* loader) { | ||||||
|     FURI_CRITICAL_ENTER(); |     if(loader->app.thread) { | ||||||
|     bool result = false; |         return false; | ||||||
|     if(instance->lock_count == 0) { |  | ||||||
|         instance->lock_count++; |  | ||||||
|         result = true; |  | ||||||
|     } |     } | ||||||
|     FURI_CRITICAL_EXIT(); | 
 | ||||||
|     return result; |     loader->app.thread = (FuriThread*)LOADER_MAGIC_THREAD_VALUE; | ||||||
|  |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void loader_unlock(Loader* instance) { | static void loader_do_unlock(Loader* loader) { | ||||||
|     FURI_CRITICAL_ENTER(); |     furi_assert(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE); | ||||||
|     if(instance->lock_count > 0) instance->lock_count--; |     loader->app.thread = NULL; | ||||||
|     FURI_CRITICAL_EXIT(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool loader_is_locked(const Loader* instance) { | static void loader_do_app_closed(Loader* loader) { | ||||||
|     return instance->lock_count > 0; |     furi_assert(loader->app.thread); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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()); |     FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); | ||||||
| 
 |     if(loader->app.args) { | ||||||
|         if(loader_instance->application_arguments) { |         free(loader->app.args); | ||||||
|             free(loader_instance->application_arguments); |         loader->app.args = NULL; | ||||||
|             loader_instance->application_arguments = NULL; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         if(!(loader_instance->application->flags & FlipperApplicationFlagInsomniaSafe)) { |     if(loader->app.insomniac) { | ||||||
|         furi_hal_power_insomnia_exit(); |         furi_hal_power_insomnia_exit(); | ||||||
|     } |     } | ||||||
|         loader_unlock(instance); |  | ||||||
| 
 | 
 | ||||||
|         event.type = LoaderEventTypeApplicationStopped; |     free(loader->app.name); | ||||||
|         furi_pubsub_publish(loader_instance->pubsub, &event); |     loader->app.name = NULL; | ||||||
|     } | 
 | ||||||
|  |     furi_thread_free(loader->app.thread); | ||||||
|  |     loader->app.thread = NULL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static uint32_t loader_hide_menu(void* context) { | // app
 | ||||||
|     UNUSED(context); |  | ||||||
|     return VIEW_NONE; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static uint32_t loader_back_to_primary_menu(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     Submenu* submenu = context; |  | ||||||
|     submenu_set_selected_item(submenu, 0); |  | ||||||
|     return LoaderMenuViewPrimary; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void loader_free(Loader* instance) { |  | ||||||
|     furi_assert(instance); |  | ||||||
| 
 |  | ||||||
|     if(instance->cli) { |  | ||||||
|         furi_record_close(RECORD_CLI); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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); |  | ||||||
| 
 |  | ||||||
|     free(instance); |  | ||||||
|     instance = 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]); |  | ||||||
|     } |  | ||||||
|     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]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_show_menu() { |  | ||||||
|     furi_assert(loader_instance); |  | ||||||
|     furi_thread_flags_set(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void loader_update_menu() { |  | ||||||
|     menu_reset(loader_instance->primary_menu); |  | ||||||
|     loader_build_menu(); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| int32_t loader_srv(void* p) { | int32_t loader_srv(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|  |     Loader* loader = loader_alloc(); | ||||||
|  |     furi_record_create(RECORD_LOADER, loader); | ||||||
|  | 
 | ||||||
|     FURI_LOG_I(TAG, "Executing system start hooks"); |     FURI_LOG_I(TAG, "Executing system start hooks"); | ||||||
|     for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { |     for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { | ||||||
|         FLIPPER_ON_SYSTEM_START[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)) { |     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) { |     LoaderMessage message; | ||||||
|         uint32_t flags = |     while(true) { | ||||||
|             furi_thread_flags_wait(LOADER_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); |         if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { | ||||||
|         if(flags & LOADER_THREAD_FLAG_SHOW_MENU) { |             switch(message.type) { | ||||||
|             menu_set_selected_item(loader_instance->primary_menu, 0); |             case LoaderMessageTypeStartByName: | ||||||
|             view_dispatcher_switch_to_view( |                 message.status_value->value = | ||||||
|                 loader_instance->view_dispatcher, LoaderMenuViewPrimary); |                     loader_do_start_by_name(loader, message.start.name, message.start.args); | ||||||
|             view_dispatcher_run(loader_instance->view_dispatcher); |                 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; |     return 0; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| FuriPubSub* loader_get_pubsub(Loader* instance) { |  | ||||||
|     return instance->pubsub; |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,7 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | #include <furi.h> | ||||||
| #include <core/pubsub.h> |  | ||||||
| #include <stdbool.h> |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -43,13 +41,10 @@ bool loader_lock(Loader* instance); | |||||||
| void loader_unlock(Loader* instance); | void loader_unlock(Loader* instance); | ||||||
| 
 | 
 | ||||||
| /** Get loader lock status */ | /** Get loader lock status */ | ||||||
| bool loader_is_locked(const Loader* instance); | bool loader_is_locked(Loader* instance); | ||||||
| 
 | 
 | ||||||
| /** Show primary loader */ | /** Show primary loader */ | ||||||
| void loader_show_menu(); | void loader_show_menu(Loader* instance); | ||||||
| 
 |  | ||||||
| /** Show primary loader */ |  | ||||||
| void loader_update_menu(); |  | ||||||
| 
 | 
 | ||||||
| /** Show primary loader */ | /** Show primary loader */ | ||||||
| FuriPubSub* loader_get_pubsub(Loader* instance); | FuriPubSub* loader_get_pubsub(Loader* instance); | ||||||
|  | |||||||
							
								
								
									
										117
									
								
								applications/services/loader/loader_cli.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								applications/services/loader/loader_cli.c
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
|  | } | ||||||
| @ -1,39 +1,56 @@ | |||||||
| #include "loader.h" | #pragma once | ||||||
| 
 |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <toolbox/api_lock.h> | ||||||
| #include <core/pubsub.h> | #include "loader.h" | ||||||
| #include <cli/cli.h> | #include "loader_menu.h" | ||||||
| #include <lib/toolbox/args.h> |  | ||||||
| 
 | 
 | ||||||
| #include <gui/view_dispatcher.h> | typedef struct { | ||||||
| 
 |     char* args; | ||||||
| #include <gui/modules/menu.h> |     char* name; | ||||||
| #include <gui/modules/submenu.h> |     FuriThread* thread; | ||||||
| 
 |     bool insomniac; | ||||||
| #include <applications.h> | } LoaderAppData; | ||||||
| #include <assets_icons.h> |  | ||||||
| 
 | 
 | ||||||
| struct Loader { | 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; |     FuriPubSub* pubsub; | ||||||
|  |     FuriMessageQueue* queue; | ||||||
|  |     LoaderMenu* loader_menu; | ||||||
|  |     LoaderAppData app; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     LoaderMenuViewPrimary, |     LoaderMessageTypeStartByName, | ||||||
|     LoaderMenuViewSettings, |     LoaderMessageTypeAppClosed, | ||||||
| } LoaderMenuView; |     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
									
								
								applications/services/loader/loader_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								applications/services/loader/loader_menu.c
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								applications/services/loader/loader_menu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/services/loader/loader_menu.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
| @ -43,7 +43,6 @@ static void debug_changed(VariableItem* item) { | |||||||
|     } else { |     } else { | ||||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); |         furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); | ||||||
|     } |     } | ||||||
|     loader_update_menu(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const char* const heap_trace_mode_text[] = { | const char* const heap_trace_mode_text[] = { | ||||||
| @ -137,8 +136,6 @@ static void hand_orient_changed(VariableItem* item) { | |||||||
|     } else { |     } else { | ||||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient); |         furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     loader_update_menu(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const char* const sleep_method[] = { | const char* const sleep_method[] = { | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,23.3,, | Version,+,24.0,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.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,-,llabs,long long,long long | ||||||
| Function,-,lldiv,lldiv_t,"long long, long long" | Function,-,lldiv,lldiv_t,"long long, long long" | ||||||
| Function,+,loader_get_pubsub,FuriPubSub*,Loader* | 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_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_start,LoaderStatus,"Loader*, const char*, const char*" | ||||||
| Function,+,loader_unlock,void,Loader* | Function,+,loader_unlock,void,Loader* | ||||||
| Function,+,loader_update_menu,void, |  | ||||||
| Function,+,loading_alloc,Loading*, | Function,+,loading_alloc,Loading*, | ||||||
| Function,+,loading_free,void,Loading* | Function,+,loading_free,void,Loading* | ||||||
| Function,+,loading_get_view,View*,Loading* | Function,+,loading_get_view,View*,Loading* | ||||||
|  | |||||||
| 
 | 
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,23.3,, | Version,+,24.0,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.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,-,llroundf,long long int,float | ||||||
| Function,-,llroundl,long long int,long double | Function,-,llroundl,long long int,long double | ||||||
| Function,+,loader_get_pubsub,FuriPubSub*,Loader* | 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_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_start,LoaderStatus,"Loader*, const char*, const char*" | ||||||
| Function,+,loader_unlock,void,Loader* | Function,+,loader_unlock,void,Loader* | ||||||
| Function,+,loader_update_menu,void, |  | ||||||
| Function,+,loading_alloc,Loading*, | Function,+,loading_alloc,Loading*, | ||||||
| Function,+,loading_free,void,Loading* | Function,+,loading_free,void,Loading* | ||||||
| Function,+,loading_get_view,View*,Loading* | Function,+,loading_get_view,View*,Loading* | ||||||
|  | |||||||
| 
 | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sergey Gavrilov
						Sergey Gavrilov