diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index bf1cd2be6..b4c534932 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -69,11 +69,6 @@ void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) { view_dispatcher); } -void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher, void* context) { - furi_check(view_dispatcher); - view_dispatcher->event_context = context; -} - void view_dispatcher_set_navigation_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherNavigationEventCallback callback) { @@ -97,6 +92,11 @@ void view_dispatcher_set_tick_event_callback( view_dispatcher->tick_period = tick_period; } +void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher, void* context) { + furi_check(view_dispatcher); + view_dispatcher->event_context = context; +} + FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher) { furi_check(view_dispatcher); furi_check(view_dispatcher->event_loop); diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 7627e5a0b..905c60975 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -107,7 +107,7 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher, * in view_dispatcher_run. * * You can add your objects into event_loop instance, but don't run the loop on - * your side it will cause issues with input processing on dispatcher stop. + * your side as it will cause issues with input processing on dispatcher stop. * * @param view_dispatcher ViewDispatcher instance * diff --git a/applications/services/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h index fcf426c31..46a4ac7fa 100644 --- a/applications/services/gui/view_dispatcher_i.h +++ b/applications/services/gui/view_dispatcher_i.h @@ -11,7 +11,7 @@ #include "view_i.h" #include "gui_i.h" -DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) +DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) // NOLINT struct ViewDispatcher { FuriEventLoop* event_loop; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index de013381f..f22441481 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -244,6 +244,43 @@ FuriPubSub* loader_get_pubsub(Loader* loader) { return loader->pubsub; } +bool loader_signal(Loader* loader, uint32_t signal, void* arg) { + furi_check(loader); + + LoaderMessageBoolResult result; + + LoaderMessage message = { + .type = LoaderMessageTypeSignal, + .api_lock = api_lock_alloc_locked(), + .signal.signal = signal, + .signal.arg = arg, + .bool_value = &result, + }; + + furi_message_queue_put(loader->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return result.value; +} + +bool loader_get_application_name(Loader* loader, FuriString* name) { + furi_check(loader); + + LoaderMessageBoolResult result; + + LoaderMessage message = { + .type = LoaderMessageTypeGetApplicationName, + .api_lock = api_lock_alloc_locked(), + .application_name = name, + .bool_value = &result, + }; + + furi_message_queue_put(loader->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return result.value; +} + // callbacks static void loader_menu_closed_callback(void* context) { @@ -654,6 +691,28 @@ static void loader_do_app_closed(Loader* loader) { furi_pubsub_publish(loader->pubsub, &event); } +static bool loader_is_application_running(Loader* loader) { + FuriThread* app_thread = loader->app.thread; + return app_thread && (app_thread != (FuriThread*)LOADER_MAGIC_THREAD_VALUE); +} + +static bool loader_do_signal(Loader* loader, uint32_t signal, void* arg) { + if(loader_is_application_running(loader)) { + return furi_thread_signal(loader->app.thread, signal, arg); + } + + return false; +} + +static bool loader_do_get_application_name(Loader* loader, FuriString* name) { + if(loader_is_application_running(loader)) { + furi_string_set(name, furi_thread_get_name(loader->app.thread)); + return true; + } + + return false; +} + // app int32_t loader_srv(void* p) { @@ -714,9 +773,19 @@ int32_t loader_srv(void* p) { case LoaderMessageTypeApplicationsClosed: loader_do_applications_closed(loader); break; + case LoaderMessageTypeSignal: + message.bool_value->value = + loader_do_signal(loader, message.signal.signal, message.signal.arg); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeGetApplicationName: + message.bool_value->value = + loader_do_get_application_name(loader, message.application_name); + api_lock_unlock(message.api_lock); + break; } } } return 0; -} \ No newline at end of file +} diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index ae914117d..421b6916d 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -88,6 +88,26 @@ void loader_show_menu(Loader* instance); */ FuriPubSub* loader_get_pubsub(Loader* instance); +/** + * @brief Send a signal to the currently running application + * + * @param[in] instance pointer to the loader instance + * @param[in] signal signal value to be sent + * @param[in,out] arg optional argument (can be of any value, including NULL) + * + * @return true if the signal was handled by the application, false otherwise + */ +bool loader_signal(Loader* instance, uint32_t signal, void* arg); + +/** + * @brief Get the name of the currently running application + * + * @param[in] instance pointer to the loader instance + * @param[in,out] name pointer to the string to contain the name (must be allocated) + * @return true if it was possible to get an application name, false otherwise + */ +bool loader_get_application_name(Loader* instance, FuriString* name); + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index 1fbd0035e..a0254f0d0 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -1,8 +1,10 @@ +#include "loader.h" + #include #include #include #include -#include "loader.h" +#include static void loader_cli_print_usage(void) { printf("Usage:\r\n"); @@ -11,6 +13,8 @@ static void loader_cli_print_usage(void) { printf("\tlist\t - List available applications\r\n"); printf("\topen \t - Open application by name\r\n"); printf("\tinfo\t - Show loader state\r\n"); + printf("\tclose\t - Close the current application\r\n"); + printf("\tsignal [arg:hex]\t - Send a signal with an optional argument\r\n"); } static void loader_cli_list(void) { @@ -25,12 +29,15 @@ static void loader_cli_list(void) { } static void loader_cli_info(Loader* loader) { - if(!loader_is_locked(loader)) { + FuriString* app_name = furi_string_alloc(); + + if(!loader_get_application_name(loader, app_name)) { printf("No application is running\r\n"); } else { - // TODO FL-3513: print application name ??? - printf("Application is running\r\n"); + printf("Application \"%s\" is running\r\n", furi_string_get_cstr(app_name)); } + + furi_string_free(app_name); } static void loader_cli_open(FuriString* args, Loader* loader) { @@ -53,6 +60,12 @@ static void loader_cli_open(FuriString* args, Loader* loader) { FuriString* error_message = furi_string_alloc(); if(loader_start(loader, app_name_str, args_str, error_message) != LoaderStatusOk) { printf("%s\r\n", furi_string_get_cstr(error_message)); + } else { +#ifdef SRV_NOTIFICATION + NotificationApp* notification_srv = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification_srv, &sequence_display_backlight_on); + furi_record_close(RECORD_NOTIFICATION); +#endif } furi_string_free(error_message); } while(false); @@ -60,6 +73,38 @@ static void loader_cli_open(FuriString* args, Loader* loader) { furi_string_free(app_name); } +static void loader_cli_close(Loader* loader) { + FuriString* app_name = furi_string_alloc(); + + if(!loader_get_application_name(loader, app_name)) { + printf("No application is running\r\n"); + } else if(!loader_signal(loader, FuriSignalExit, NULL)) { + printf("Application \"%s\" has to be closed manually\r\n", furi_string_get_cstr(app_name)); + } else { + printf("Application \"%s\" was closed\r\n", furi_string_get_cstr(app_name)); + } + + furi_string_free(app_name); +} + +static void loader_cli_signal(FuriString* args, Loader* loader) { + uint32_t signal; + void* arg = NULL; + + if(!sscanf(furi_string_get_cstr(args), "%lu %p", &signal, &arg)) { + printf("Signal must be a decimal number\r\n"); + } else if(!loader_is_locked(loader)) { + printf("No application is running\r\n"); + } else { + const bool is_handled = loader_signal(loader, signal, arg); + printf( + "Signal %lu with argument 0x%p was %s\r\n", + signal, + arg, + is_handled ? "handled" : "ignored"); + } +} + static void loader_cli(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); @@ -68,29 +113,21 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) { 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; - } - + if(!args_read_string_and_trim(args, cmd)) { loader_cli_print_usage(); - } while(false); + } else if(furi_string_equal(cmd, "list")) { + loader_cli_list(); + } else if(furi_string_equal(cmd, "open")) { + loader_cli_open(args, loader); + } else if(furi_string_equal(cmd, "info")) { + loader_cli_info(loader); + } else if(furi_string_equal(cmd, "close")) { + loader_cli_close(loader); + } else if(furi_string_equal(cmd, "signal")) { + loader_cli_signal(args, loader); + } else { + loader_cli_print_usage(); + } furi_string_free(cmd); furi_record_close(RECORD_LOADER); @@ -104,4 +141,4 @@ void loader_on_system_start(void) { #else UNUSED(loader_cli); #endif -} \ No newline at end of file +} diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 95c0c0006..92f1e88e0 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -31,6 +31,8 @@ typedef enum { LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, LoaderMessageTypeStartByNameDetachedWithGuiError, + LoaderMessageTypeSignal, + LoaderMessageTypeGetApplicationName, } LoaderMessageType; typedef struct { @@ -39,6 +41,11 @@ typedef struct { FuriString* error_message; } LoaderMessageStartByName; +typedef struct { + uint32_t signal; + void* arg; +} LoaderMessageSignal; + typedef enum { LoaderStatusErrorUnknown, LoaderStatusErrorInvalidFile, @@ -65,6 +72,8 @@ typedef struct { union { LoaderMessageStartByName start; + LoaderMessageSignal signal; + FuriString* application_name; }; union { diff --git a/furi/core/base.h b/furi/core/base.h index 92a52a797..e89065a7d 100644 --- a/furi/core/base.h +++ b/furi/core/base.h @@ -40,6 +40,12 @@ typedef enum { FuriStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization. } FuriStatus; +typedef enum { + FuriSignalExit, /**< Request (graceful) exit. */ + // Other standard signals may be added in the future + FuriSignalCustom = 100, /**< Custom signal values start from here. */ +} FuriSignal; + #ifdef __cplusplus } #endif diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index b939695dd..f38a67657 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -149,6 +149,9 @@ void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); + furi_thread_set_signal_callback( + instance->thread_id, furi_event_loop_signal_callback, instance); + uint32_t timeout = instance->tick_callback ? instance->tick_interval : FuriWaitForever; while(true) { @@ -194,11 +197,12 @@ void furi_event_loop_run(FuriEventLoop* instance) { } instance->state = FuriEventLoopStateIdle; } + + furi_thread_set_signal_callback(instance->thread_id, NULL, NULL); } void furi_event_loop_stop(FuriEventLoop* instance) { furi_check(instance); - furi_check(instance->thread_id == furi_thread_get_current_id()); xTaskNotifyIndexed( instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagStop, eSetBits); @@ -366,4 +370,19 @@ void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent } FURI_CRITICAL_EXIT(); -} \ No newline at end of file +} + +bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { + furi_assert(context); + FuriEventLoop* instance = context; + UNUSED(arg); + + switch(signal) { + case FuriSignalExit: + furi_event_loop_stop(instance); + return true; + // Room for possible other standard signal handlers + default: + return false; + } +} diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 8ddd10966..5c0b144a1 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -28,6 +28,8 @@ typedef struct { const FuriEventLoopContractGetLevel get_level; } FuriEventLoopContract; +bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context); + #ifdef __cplusplus } #endif diff --git a/furi/core/thread.c b/furi/core/thread.c index 4e9477712..0a8c88667 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -42,6 +42,9 @@ struct FuriThread { FuriThreadStateCallback state_callback; void* state_context; + FuriThreadSignalCallback signal_callback; + void* signal_context; + char* name; char* appid; @@ -304,6 +307,29 @@ FuriThreadState furi_thread_get_state(FuriThread* thread) { return thread->state; } +void furi_thread_set_signal_callback( + FuriThread* thread, + FuriThreadSignalCallback callback, + void* context) { + furi_check(thread); + furi_check(thread->state == FuriThreadStateStopped || thread == furi_thread_get_current()); + + thread->signal_callback = callback; + thread->signal_context = context; +} + +bool furi_thread_signal(const FuriThread* thread, uint32_t signal, void* arg) { + furi_check(thread); + + bool is_consumed = false; + + if(thread->signal_callback) { + is_consumed = thread->signal_callback(signal, arg, thread->signal_context); + } + + return is_consumed; +} + void furi_thread_start(FuriThread* thread) { furi_check(thread); furi_check(thread->callback); diff --git a/furi/core/thread.h b/furi/core/thread.h index 9c113bd49..be09e040e 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -86,6 +86,18 @@ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); */ typedef void (*FuriThreadStateCallback)(FuriThreadState state, void* context); +/** + * @brief Signal handler callback function pointer type. + * + * The function to be used as a signal handler callback MUS follow this signature. + * + * @param[in] signal value of the signal to be handled by the recipient + * @param[in,out] arg optional argument (can be of any value, including NULL) + * @param[in,out] context pointer to a user-specified object + * @returns true if the signal was handled, false otherwise + */ +typedef bool (*FuriThreadSignalCallback)(uint32_t signal, void* arg, void* context); + /** * @brief Create a FuriThread instance. * @@ -255,6 +267,29 @@ void furi_thread_set_state_context(FuriThread* thread, void* context); */ FuriThreadState furi_thread_get_state(FuriThread* thread); +/** + * @brief Set a signal handler callback for a FuriThread instance. + * + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] callback pointer to a user-specified callback function + * @param[in] context pointer to a user-specified object (will be passed to the callback, can be NULL) + */ +void furi_thread_set_signal_callback( + FuriThread* thread, + FuriThreadSignalCallback callback, + void* context); + +/** + * @brief Send a signal to a FuriThread instance. + * + * @param[in] thread pointer to the FuriThread instance to be signaled + * @param[in] signal signal value to be sent + * @param[in,out] arg optional argument (can be of any value, including NULL) + */ +bool furi_thread_signal(const FuriThread* thread, uint32_t signal, void* arg); + /** * @brief Start a FuriThread instance. * diff --git a/scripts/distfap.py b/scripts/distfap.py deleted file mode 100755 index b1c558790..000000000 --- a/scripts/distfap.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 - -import os -import posixpath - -from flipper.app import App -from flipper.storage import FlipperStorage, FlipperStorageOperations -from flipper.utils.cdc import resolve_port - - -class Main(App): - def init(self): - self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") - self.parser.add_argument( - "-n", - "--no-launch", - dest="launch_app", - action="store_false", - help="Don't launch app", - ) - - self.parser.add_argument("fap_src_path", help="App file to upload") - self.parser.add_argument( - "--fap_dst_dir", help="Upload path", default="/ext/apps", required=False - ) - self.parser.set_defaults(func=self.install) - - def install(self): - if not (port := resolve_port(self.logger, self.args.port)): - return 1 - - try: - with FlipperStorage(port) as storage: - storage_ops = FlipperStorageOperations(storage) - fap_local_path = self.args.fap_src_path - self.args.fap_dst_dir = self.args.fap_dst_dir.rstrip("/\\") - - if not os.path.isfile(fap_local_path): - self.logger.error( - f"Error: source .fap ({fap_local_path}) not found" - ) - return 2 - - fap_dst_path = posixpath.join( - self.args.fap_dst_dir, os.path.basename(fap_local_path) - ) - - self.logger.info(f'Installing "{fap_local_path}" to {fap_dst_path}') - - storage_ops.recursive_send(fap_dst_path, fap_local_path, False) - - if not self.args.launch_app: - return 0 - - storage.send_and_wait_eol(f"loader open {fap_dst_path}\r") - - if len(result := storage.read.until(storage.CLI_EOL)): - self.logger.error(f"Unexpected response: {result.decode('ascii')}") - return 3 - return 0 - - except Exception as e: - self.logger.error(f"Error: {e}") - # raise - return 4 - - -if __name__ == "__main__": - Main()() diff --git a/scripts/runfap.py b/scripts/runfap.py index 42141acff..b4b5989aa 100755 --- a/scripts/runfap.py +++ b/scripts/runfap.py @@ -2,6 +2,7 @@ import operator from functools import reduce +import time from flipper.app import App from flipper.storage import FlipperStorage, FlipperStorageOperations @@ -9,6 +10,8 @@ from flipper.utils.cdc import resolve_port class Main(App): + APP_POST_CLOSE_DELAY_SEC = 0.2 + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( @@ -67,6 +70,23 @@ class Main(App): if self.args.host_app: startup_command = self.args.host_app + self.logger.info("Closing current app, if any") + for _ in range(10): + storage.send_and_wait_eol("loader close\r") + result = storage.read.until(storage.CLI_EOL) + if b"was closed" in result: + self.logger.info("App closed") + storage.read.until(storage.CLI_EOL) + time.sleep(self.APP_POST_CLOSE_DELAY_SEC) + elif result.startswith(b"No application"): + storage.read.until(storage.CLI_EOL) + break + else: + self.logger.error( + f"Unexpected response: {result.decode('ascii')}" + ) + return 4 + self.logger.info(f"Launching app: {startup_command}") storage.send_and_wait_eol(f"loader open {startup_command}\r") diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 99c153c04..88bb48301 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,66.0,, +Version,+,66.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1627,10 +1627,12 @@ Function,+,furi_thread_set_context,void,"FuriThread*, void*" Function,+,furi_thread_set_current_priority,void,FuriThreadPriority Function,+,furi_thread_set_name,void,"FuriThread*, const char*" Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" +Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCallback, void*" Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" @@ -1782,10 +1784,12 @@ Function,-,llrintl,long long int,long double Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double +Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* +Function,+,loader_signal,_Bool,"Loader*, uint32_t, void*" Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b59453823..3e68bac5c 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,66.0,, +Version,+,66.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1841,10 +1841,12 @@ Function,+,furi_thread_set_context,void,"FuriThread*, void*" Function,+,furi_thread_set_current_priority,void,FuriThreadPriority Function,+,furi_thread_set_name,void,"FuriThread*, const char*" Function,+,furi_thread_set_priority,void,"FuriThread*, FuriThreadPriority" +Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCallback, void*" Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" @@ -2200,10 +2202,12 @@ Function,-,llrintl,long long int,long double Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double +Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* +Function,+,loader_signal,_Bool,"Loader*, uint32_t, void*" Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*"