mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-23 03:29:57 -07:00
Merge remote-tracking branch 'ofw/dev' into mntm-dev
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -68,3 +68,7 @@ PVS-Studio.log
|
||||
|
||||
# JS packages
|
||||
node_modules/
|
||||
|
||||
# cli_perf script output in case of errors
|
||||
/block.bin
|
||||
/return_block.bin
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
entry_point="accessor_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=40,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -8,6 +8,5 @@ App(
|
||||
"power",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="blink_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -13,6 +13,5 @@ App(
|
||||
"bt_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=110,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -10,6 +10,5 @@ App(
|
||||
"ccid_test",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="direct_draw_app",
|
||||
requires=["gui", "input"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
requires=["gui"],
|
||||
fap_libs=["u8g2"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="event_loop_blink_test_app",
|
||||
requires=["input"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
requires=["expansion_start"],
|
||||
fap_libs=["assets"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
fap_file_assets="assets",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="file_browser_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=150,
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="keypad_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=30,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -11,6 +11,5 @@ App(
|
||||
"lfrfid_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=100,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
8
applications/debug/loader_chaining_a/application.fam
Normal file
8
applications/debug/loader_chaining_a/application.fam
Normal file
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="loader_chaining_a",
|
||||
name="Loader Chaining Test: App A",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="chaining_test_app_a",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Debug",
|
||||
)
|
||||
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <loader/loader.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#define TAG "LoaderChainingA"
|
||||
#define CHAINING_TEST_B "/ext/apps/Debug/loader_chaining_b.fap"
|
||||
#define NONEXISTENT_APP "Some nonexistent app"
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
|
||||
Loader* loader;
|
||||
|
||||
DialogsApp* dialogs;
|
||||
} LoaderChainingA;
|
||||
|
||||
typedef enum {
|
||||
LoaderChainingASubmenuLaunchB,
|
||||
LoaderChainingASubmenuLaunchBThenA,
|
||||
LoaderChainingASubmenuLaunchNonexistentSilent,
|
||||
LoaderChainingASubmenuLaunchNonexistentGui,
|
||||
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
|
||||
} LoaderChainingASubmenu;
|
||||
|
||||
static void loader_chaining_a_submenu_callback(void* context, uint32_t index) {
|
||||
LoaderChainingA* app = context;
|
||||
|
||||
FuriString* self_path = furi_string_alloc();
|
||||
furi_check(loader_get_application_launch_path(app->loader, self_path));
|
||||
|
||||
switch(index) {
|
||||
case LoaderChainingASubmenuLaunchB:
|
||||
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchBThenA:
|
||||
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
app->loader,
|
||||
furi_string_get_cstr(self_path),
|
||||
"Hello to you from the future",
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentSilent:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagNone);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentGui:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentGuiThenA:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
app->loader,
|
||||
furi_string_get_cstr(self_path),
|
||||
"Hello to you from the future",
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(self_path);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
|
||||
static bool loader_chaining_a_nav_callback(void* context) {
|
||||
LoaderChainingA* app = context;
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
LoaderChainingA* loader_chaining_a_alloc(void) {
|
||||
LoaderChainingA* app = malloc(sizeof(LoaderChainingA));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->loader = furi_record_open(RECORD_LOADER);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->submenu = submenu_alloc();
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Launch B",
|
||||
LoaderChainingASubmenuLaunchB,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Launch B, then A",
|
||||
LoaderChainingASubmenuLaunchBThenA,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Trigger error: silent",
|
||||
LoaderChainingASubmenuLaunchNonexistentSilent,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Trigger error: GUI",
|
||||
LoaderChainingASubmenuLaunchNonexistentGui,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Error, then launch A",
|
||||
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu));
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, loader_chaining_a_nav_callback);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void loader_chaining_a_free(LoaderChainingA* app) {
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, 0);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t chaining_test_app_a(const char* arg) {
|
||||
LoaderChainingA* app = loader_chaining_a_alloc();
|
||||
|
||||
if(arg) {
|
||||
if(strlen(arg)) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
FuriString* text;
|
||||
|
||||
dialog_message_set_header(message, "Hi, I am A", 64, 0, AlignCenter, AlignTop);
|
||||
text = furi_string_alloc_printf("Me from the past says:\n%s", arg);
|
||||
dialog_message_set_buttons(message, NULL, "ok!", NULL);
|
||||
|
||||
dialog_message_set_text(
|
||||
message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_show(app->dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(text);
|
||||
}
|
||||
}
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
loader_chaining_a_free(app);
|
||||
return 0;
|
||||
}
|
||||
8
applications/debug/loader_chaining_b/application.fam
Normal file
8
applications/debug/loader_chaining_b/application.fam
Normal file
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="loader_chaining_b",
|
||||
name="Loader Chaining Test: App B",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="chaining_test_app_b",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Debug",
|
||||
)
|
||||
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <furi.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
int32_t chaining_test_app_b(const char* arg) {
|
||||
if(!arg) return 0;
|
||||
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Hi, I am B", 64, 0, AlignCenter, AlignTop);
|
||||
FuriString* text = furi_string_alloc_printf("And A told me:\n%s", arg);
|
||||
dialog_message_set_text(message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_set_buttons(message, "Just quit", NULL, "Launch A");
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(text);
|
||||
|
||||
if(result == DialogMessageButtonRight)
|
||||
loader_enqueue_launch(
|
||||
loader, "/ext/apps/Debug/loader_chaining_a.fap", NULL, LoaderDeferredLaunchFlagGui);
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
return 0;
|
||||
}
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="locale_test_app",
|
||||
requires=["gui", "locale"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="rpc_debug_app",
|
||||
requires=["gui", "rpc_start", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="speaker_debug_app",
|
||||
requires=["gui", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
fap_libs=["music_worker"],
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <notification/notification.h>
|
||||
#include <music_worker/music_worker.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
|
||||
#define TAG "SpeakerDebug"
|
||||
|
||||
@@ -20,14 +21,14 @@ typedef struct {
|
||||
typedef struct {
|
||||
MusicWorker* music_worker;
|
||||
FuriMessageQueue* message_queue;
|
||||
Cli* cli;
|
||||
CliRegistry* cli_registry;
|
||||
} SpeakerDebugApp;
|
||||
|
||||
static SpeakerDebugApp* speaker_app_alloc(void) {
|
||||
SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
|
||||
app->music_worker = music_worker_alloc();
|
||||
app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage));
|
||||
app->cli = furi_record_open(RECORD_CLI);
|
||||
app->cli_registry = furi_record_open(RECORD_CLI);
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -96,7 +97,8 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
|
||||
cli_registry_add_command(
|
||||
app->cli_registry, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
|
||||
|
||||
SpeakerDebugAppMessage message;
|
||||
FuriStatus status;
|
||||
@@ -111,7 +113,7 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
cli_delete_command(app->cli, CLI_COMMAND);
|
||||
cli_registry_delete_command(app->cli_registry, CLI_COMMAND);
|
||||
}
|
||||
|
||||
int32_t speaker_debug_app(void* arg) {
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
entry_point="subghz_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=50,
|
||||
fap_icon="subghz_test_10px.png",
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="images",
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_element_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_view_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="uart_echo_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ App(
|
||||
requires=["system_settings", "cli_subghz"],
|
||||
provides=["delay_test"],
|
||||
resources="resources",
|
||||
order=100,
|
||||
order=30,
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -18,7 +18,7 @@ App(
|
||||
entry_point="delay_test_app",
|
||||
stack_size=1 * 1024,
|
||||
requires=["unit_tests"],
|
||||
order=110,
|
||||
order=30,
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "tests/test_api.h"
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <rpc/rpc.h>
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <cli/cli.h>
|
||||
#include <storage/storage.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/run_parallel.h>
|
||||
|
||||
#include "test_runner.h"
|
||||
@@ -27,8 +29,9 @@ static int32_t unit_tests_thread(void* context) {
|
||||
|
||||
void unit_tests_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
|
||||
CliRegistry* registry = furi_record_open(RECORD_CLI);
|
||||
cli_registry_add_command(
|
||||
registry, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#endif
|
||||
if(furi_hal_is_normal_boot()) {
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_mouse_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=60,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="vibro_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
Submodule applications/external updated: 43dac9eeaa...59c83bc0ca
@@ -6,7 +6,7 @@ App(
|
||||
cdefines=["APP_ARCHIVE"],
|
||||
requires=["gui"],
|
||||
stack_size=6 * 1024,
|
||||
order=0,
|
||||
order=10,
|
||||
sdk_headers=[
|
||||
"helpers/archive_helpers_ext.h",
|
||||
],
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#include "archive_browser.h"
|
||||
#include <applications/main/u2f/u2f_data.h>
|
||||
|
||||
static const char* known_apps[] = {
|
||||
static const char* const known_apps[] = {
|
||||
[ArchiveAppTypeU2f] = "u2f",
|
||||
[ArchiveAppTypeSetting] = "setting",
|
||||
[ArchiveAppTypeSearch] = "search",
|
||||
};
|
||||
|
||||
@@ -27,23 +28,24 @@ bool archive_app_is_available(void* context, const char* path) {
|
||||
furi_assert(path);
|
||||
|
||||
ArchiveAppTypeEnum app = archive_get_app_type(path);
|
||||
bool res = false;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
switch(app) {
|
||||
case ArchiveAppTypeU2f:
|
||||
res = storage_file_exists(storage, U2F_KEY_FILE) &&
|
||||
storage_file_exists(storage, U2F_CNT_FILE);
|
||||
break;
|
||||
case ArchiveAppTypeSearch:
|
||||
res = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if(app == ArchiveAppTypeU2f) {
|
||||
bool file_exists = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
if(storage_file_exists(storage, U2F_KEY_FILE)) {
|
||||
file_exists = storage_file_exists(storage, U2F_CNT_FILE);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return file_exists;
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
return true;
|
||||
} else if(app == ArchiveAppTypeSearch) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool archive_app_read_dir(void* context, const char* path) {
|
||||
@@ -53,15 +55,19 @@ bool archive_app_read_dir(void* context, const char* path) {
|
||||
|
||||
ArchiveAppTypeEnum app = archive_get_app_type(path);
|
||||
|
||||
switch(app) {
|
||||
case ArchiveAppTypeU2f:
|
||||
if(app == ArchiveAppTypeU2f) {
|
||||
archive_file_array_rm_all(browser);
|
||||
archive_add_app_item(browser, "/app:u2f/U2F Token");
|
||||
return true;
|
||||
case ArchiveAppTypeSearch:
|
||||
return true;
|
||||
default:
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
archive_file_array_rm_all(browser);
|
||||
archive_add_app_item(browser, path);
|
||||
return true;
|
||||
} else if(app == ArchiveAppTypeSearch) {
|
||||
// Keep results when backing out from Info/Show scene
|
||||
// First search button item added when switching tab
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -74,21 +80,20 @@ void archive_app_delete_file(void* context, const char* path) {
|
||||
ArchiveAppTypeEnum app = archive_get_app_type(path);
|
||||
bool res = false;
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
switch(app) {
|
||||
case ArchiveAppTypeU2f:
|
||||
res = (storage_common_remove(fs_api, U2F_KEY_FILE) == FSE_OK);
|
||||
res |= (storage_common_remove(fs_api, U2F_CNT_FILE) == FSE_OK);
|
||||
if(app == ArchiveAppTypeU2f) {
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
res = (storage_common_remove(fs_api, EXT_PATH("u2f/key.u2f")) == FSE_OK);
|
||||
res |= (storage_common_remove(fs_api, EXT_PATH("u2f/cnt.u2f")) == FSE_OK);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(archive_is_favorite("/app:u2f/U2F Token")) {
|
||||
archive_favorites_delete("/app:u2f/U2F Token");
|
||||
}
|
||||
break;
|
||||
case ArchiveAppTypeSearch:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
// can't delete a setting!
|
||||
} else if(app == ArchiveAppTypeSearch) {
|
||||
// can't delete the search button!
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(res) {
|
||||
archive_file_array_rm_selected(browser);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
typedef enum {
|
||||
ArchiveAppTypeU2f,
|
||||
ArchiveAppTypeSetting,
|
||||
ArchiveAppTypeSearch,
|
||||
ArchiveAppTypeUnknown,
|
||||
ArchiveAppsTotal,
|
||||
@@ -11,6 +12,7 @@ typedef enum {
|
||||
|
||||
static const ArchiveFileTypeEnum app_file_types[] = {
|
||||
[ArchiveAppTypeU2f] = ArchiveFileTypeU2f,
|
||||
[ArchiveAppTypeSetting] = ArchiveFileTypeSetting,
|
||||
[ArchiveAppTypeSearch] = ArchiveFileTypeSearch,
|
||||
[ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown,
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ static void
|
||||
ArchiveTabEnum tab = archive_get_tab(browser);
|
||||
|
||||
if((item_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser) &&
|
||||
(tab != ArchiveTabDiskImage || !browser->disk_image)) {
|
||||
(tab != ArchiveTabSearch) && (tab != ArchiveTabDiskImage || !browser->disk_image)) {
|
||||
archive_switch_tab(browser, browser->last_tab_switch_dir);
|
||||
} else if(!furi_string_start_with_str(browser->path, "/app:")) {
|
||||
with_view_model(
|
||||
@@ -561,7 +561,7 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
|
||||
bool tab_empty = true;
|
||||
bool is_app_tab = furi_string_start_with_str(browser->path, "/app:");
|
||||
if(tab == ArchiveTabFavorites) {
|
||||
if(archive_favorites_count() > 0) {
|
||||
if(archive_favorites_count(browser) > 0) {
|
||||
tab_empty = false;
|
||||
}
|
||||
} else if(is_app_tab) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#define TAB_DEFAULT ArchiveTabFavorites // Start tab
|
||||
#define FILE_LIST_BUF_LEN 50
|
||||
|
||||
static const char* tab_default_paths[] = {
|
||||
static const char* const tab_default_paths[] = {
|
||||
[ArchiveTabFavorites] = "/app:favorites",
|
||||
[ArchiveTabIButton] = EXT_PATH("ibutton"),
|
||||
[ArchiveTabNFC] = EXT_PATH("nfc"),
|
||||
@@ -23,8 +23,7 @@ static const char* tab_default_paths[] = {
|
||||
[ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX,
|
||||
};
|
||||
|
||||
static const char* known_ext[] = {
|
||||
// clang-format off
|
||||
static const char* const known_ext[] = {
|
||||
[ArchiveFileTypeIButton] = ".ibtn",
|
||||
[ArchiveFileTypeNFC] = ".nfc",
|
||||
[ArchiveFileTypeSubGhz] = ".sub",
|
||||
@@ -46,7 +45,7 @@ static const char* known_ext[] = {
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
[ArchiveFileTypeAppOrJs] = ".fap|.js",
|
||||
// clang-format on
|
||||
[ArchiveFileTypeSetting] = "?",
|
||||
};
|
||||
|
||||
static const ArchiveFileTypeEnum known_type[] = {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#include "archive_favorites.h"
|
||||
#include "archive_files.h"
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#define ARCHIVE_FAV_FILE_BUF_LEN 32
|
||||
|
||||
static bool archive_favorites_read_line(File* file, FuriString* str_result) {
|
||||
@@ -46,7 +47,9 @@ static bool archive_favorites_read_line(File* file, FuriString* str_result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t archive_favorites_count(void) {
|
||||
uint16_t archive_favorites_count(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(fs_api);
|
||||
|
||||
@@ -57,7 +60,10 @@ uint16_t archive_favorites_count(void) {
|
||||
uint16_t lines = 0;
|
||||
|
||||
if(result) {
|
||||
while(archive_favorites_read_line(file, buffer)) {
|
||||
while(1) {
|
||||
if(!archive_favorites_read_line(file, buffer)) {
|
||||
break;
|
||||
}
|
||||
if(!furi_string_size(buffer)) {
|
||||
continue; // Skip empty lines
|
||||
}
|
||||
@@ -82,7 +88,10 @@ static bool archive_favourites_rescan(void) {
|
||||
|
||||
bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
if(result) {
|
||||
while(archive_favorites_read_line(file, buffer)) {
|
||||
while(1) {
|
||||
if(!archive_favorites_read_line(file, buffer)) {
|
||||
break;
|
||||
}
|
||||
if(!furi_string_size(buffer)) {
|
||||
continue;
|
||||
}
|
||||
@@ -133,7 +142,10 @@ bool archive_favorites_read(void* context) {
|
||||
bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
|
||||
if(result) {
|
||||
while(archive_favorites_read_line(file, buffer)) {
|
||||
while(1) {
|
||||
if(!archive_favorites_read_line(file, buffer)) {
|
||||
break;
|
||||
}
|
||||
if(!furi_string_size(buffer)) {
|
||||
continue;
|
||||
}
|
||||
@@ -188,7 +200,10 @@ bool archive_favorites_delete(const char* format, ...) {
|
||||
bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
|
||||
if(result) {
|
||||
while(archive_favorites_read_line(file, buffer)) {
|
||||
while(1) {
|
||||
if(!archive_favorites_read_line(file, buffer)) {
|
||||
break;
|
||||
}
|
||||
if(!furi_string_size(buffer)) {
|
||||
continue;
|
||||
}
|
||||
@@ -229,7 +244,10 @@ bool archive_is_favorite(const char* format, ...) {
|
||||
bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
|
||||
if(result) {
|
||||
while(archive_favorites_read_line(file, buffer)) {
|
||||
while(1) {
|
||||
if(!archive_favorites_read_line(file, buffer)) {
|
||||
break;
|
||||
}
|
||||
if(!furi_string_size(buffer)) {
|
||||
continue;
|
||||
}
|
||||
@@ -317,3 +335,46 @@ void archive_favorites_save(void* context) {
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
|
||||
FuriString* setting_path = furi_string_alloc_set_str(app_name);
|
||||
if(setting) {
|
||||
furi_string_push_back(setting_path, '/');
|
||||
furi_string_cat_str(setting_path, setting);
|
||||
}
|
||||
const char* setting_path_str = furi_string_get_cstr(setting_path);
|
||||
|
||||
bool is_favorite = archive_is_favorite("/app:setting/%s", setting_path_str);
|
||||
dialog_message_set_header(
|
||||
message,
|
||||
is_favorite ? "Unpin This Setting?" : "Pin This Setting?",
|
||||
64,
|
||||
0,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
dialog_message_set_text(
|
||||
message,
|
||||
is_favorite ? "It will no longer be\naccessible from the\nFavorites menu" :
|
||||
"It will be accessible from the\nFavorites menu",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
dialog_message_set_buttons(
|
||||
message, is_favorite ? "Unpin" : "Go back", NULL, is_favorite ? "Keep pinned" : "Pin");
|
||||
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessageButton button = dialog_message_show(dialogs, message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
if(is_favorite && button == DialogMessageButtonLeft) {
|
||||
archive_favorites_delete("/app:setting/%s", setting_path_str);
|
||||
} else if(!is_favorite && button == DialogMessageButtonRight) {
|
||||
archive_file_append(ARCHIVE_FAV_PATH, "/app:setting/%s\n", setting_path_str);
|
||||
}
|
||||
|
||||
furi_string_free(setting_path);
|
||||
dialog_message_free(message);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include "archive_helpers_ext.h"
|
||||
|
||||
#define ARCHIVE_FAV_PATH EXT_PATH("favorites.txt")
|
||||
#define ARCHIVE_FAV_TEMP_PATH EXT_PATH("favorites.tmp")
|
||||
|
||||
uint16_t archive_favorites_count(void);
|
||||
uint16_t archive_favorites_count(void* context);
|
||||
bool archive_favorites_read(void* context);
|
||||
bool archive_favorites_delete(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2)));
|
||||
bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__printf__, 1, 2)));
|
||||
|
||||
@@ -23,12 +23,13 @@ typedef enum {
|
||||
ArchiveFileTypeMag,
|
||||
ArchiveFileTypeCrossRemote,
|
||||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeJS,
|
||||
ArchiveFileTypeSearch,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeDiskImage,
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeSetting,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeAppOrJs,
|
||||
ArchiveFileTypeLoading,
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Intended to be called by settings apps to handle long presses, as well as
|
||||
* internally from within the archive
|
||||
*
|
||||
* @param app_name name of the referring application
|
||||
* @param setting name of the setting, which will be both displayed to the user
|
||||
* and passed to the application as an argument upon recall
|
||||
*/
|
||||
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting);
|
||||
|
||||
bool process_favorite_launch(char** p);
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -60,7 +60,7 @@ static void archive_loader_callback(const void* message, void* context) {
|
||||
const LoaderEvent* event = message;
|
||||
ArchiveApp* archive = (ArchiveApp*)context;
|
||||
|
||||
if(event->type == LoaderEventTypeApplicationStopped) {
|
||||
if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
view_dispatcher_send_custom_event(
|
||||
archive->view_dispatcher, ArchiveBrowserEventListRefresh);
|
||||
}
|
||||
@@ -140,7 +140,22 @@ static void
|
||||
|
||||
const char* app_name = archive_get_flipper_app_name(selected->type);
|
||||
|
||||
if(selected->type == ArchiveFileTypeSearch) {
|
||||
if(selected->type == ArchiveFileTypeSetting) {
|
||||
FuriString* app_name = furi_string_alloc_set(selected->path);
|
||||
furi_string_right(app_name, furi_string_search_char(app_name, '/', 1) + 1);
|
||||
size_t slash = furi_string_search_char(app_name, '/', 1);
|
||||
if(slash != FURI_STRING_FAILURE) {
|
||||
furi_string_left(app_name, slash);
|
||||
FuriString* app_args =
|
||||
furi_string_alloc_set_str(furi_string_get_cstr(app_name) + slash + 1);
|
||||
loader_start_with_gui_error(
|
||||
loader, furi_string_get_cstr(app_name), furi_string_get_cstr(app_args));
|
||||
furi_string_free(app_args);
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(app_name), NULL);
|
||||
}
|
||||
furi_string_free(app_name);
|
||||
} else if(selected->type == ArchiveFileTypeSearch) {
|
||||
while(archive_get_tab(browser) != ArchiveTabSearch) {
|
||||
archive_switch_tab(browser, TAB_LEFT);
|
||||
}
|
||||
@@ -205,7 +220,7 @@ void archive_scene_browser_on_enter(void* context) {
|
||||
browser->is_root = true;
|
||||
|
||||
archive_browser_set_callback(browser, archive_scene_browser_callback, archive);
|
||||
if(archive_get_tab(browser) == ArchiveTabFavorites && archive_favorites_count() < 1) {
|
||||
if(archive_get_tab(browser) == ArchiveTabFavorites && archive_favorites_count(browser) < 1) {
|
||||
archive_switch_tab(browser, TAB_LEFT);
|
||||
}
|
||||
archive_update_focus(browser, archive->text_store);
|
||||
|
||||
@@ -38,6 +38,7 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeMag] = &I_mag_card_10px,
|
||||
[ArchiveFileTypeCrossRemote] = &I_xremote_10px,
|
||||
[ArchiveFileTypeU2f] = &I_u2f_10px,
|
||||
[ArchiveFileTypeSetting] = &I_settings_10px,
|
||||
[ArchiveFileTypeApplication] = &I_Apps_10px,
|
||||
[ArchiveFileTypeJS] = &I_js_script_10px,
|
||||
[ArchiveFileTypeSearch] = &I_search_10px,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "usb_uart_bridge.h"
|
||||
#include "usb_cdc.h"
|
||||
#include <cli/cli_vcp.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
@@ -240,4 +240,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <infrared.h>
|
||||
#include <infrared_worker.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
@@ -554,4 +554,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(command);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048);
|
||||
CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdarg.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/lfrfid/lfrfid_worker.h>
|
||||
#include <storage/storage.h>
|
||||
@@ -566,4 +566,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048);
|
||||
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <cli/cli.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
@@ -14,7 +14,8 @@ enum {
|
||||
SubmenuIndexDetectReader = SubmenuIndexCommonMax,
|
||||
SubmenuIndexWrite,
|
||||
SubmenuIndexUpdate,
|
||||
SubmenuIndexDictAttack
|
||||
SubmenuIndexDictAttack,
|
||||
SubmenuIndexCrackNonces,
|
||||
};
|
||||
|
||||
static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
|
||||
@@ -128,6 +129,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
|
||||
SubmenuIndexDictAttack,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Crack nonces in MFKey32",
|
||||
SubmenuIndexCrackNonces,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +201,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexDetectReader) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneSaveConfirm,
|
||||
NfcSceneSaveConfirmStateDetectReader);
|
||||
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
|
||||
dolphin_deed(DolphinDeedNfcDetectReader);
|
||||
consumed = true;
|
||||
@@ -205,6 +218,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
|
||||
} else if(event.event == SubmenuIndexCommonEdit) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexCrackNonces) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces);
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -477,6 +477,26 @@ static void nfc_show_initial_scene_for_device(NfcApp* nfc) {
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
}
|
||||
|
||||
void nfc_app_run_external(NfcApp* nfc, const char* app_path) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(app_path);
|
||||
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
|
||||
loader_enqueue_launch(loader, app_path, NULL, LoaderDeferredLaunchFlagGui);
|
||||
|
||||
FuriString* self_path = furi_string_alloc();
|
||||
furi_check(loader_get_application_launch_path(loader, self_path));
|
||||
|
||||
loader_enqueue_launch(
|
||||
loader, furi_string_get_cstr(self_path), NULL, LoaderDeferredLaunchFlagGui);
|
||||
furi_string_free(self_path);
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
|
||||
view_dispatcher_stop(nfc->view_dispatcher);
|
||||
}
|
||||
|
||||
int32_t nfc_app(void* p) {
|
||||
if(!nfc_is_hal_ready()) return 0;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <cli/cli.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
@@ -37,6 +36,7 @@
|
||||
#include "helpers/felica_auth.h"
|
||||
#include "helpers/slix_unlock.h"
|
||||
|
||||
#include <loader/loader.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/path.h>
|
||||
@@ -82,6 +82,8 @@
|
||||
#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \
|
||||
(NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc")
|
||||
|
||||
#define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap"))
|
||||
|
||||
typedef enum {
|
||||
NfcRpcStateIdle,
|
||||
NfcRpcStateEmulating,
|
||||
@@ -171,6 +173,11 @@ typedef enum {
|
||||
NfcViewDetectReader,
|
||||
} NfcView;
|
||||
|
||||
typedef enum {
|
||||
NfcSceneSaveConfirmStateDetectReader,
|
||||
NfcSceneSaveConfirmStateCrackNonces,
|
||||
} NfcSceneSaveConfirmState;
|
||||
|
||||
int32_t nfc_task(void* p);
|
||||
|
||||
void nfc_text_store_set(NfcApp* nfc, const char* text, ...);
|
||||
@@ -206,3 +213,5 @@ bool nfc_save_file(NfcApp* instance, FuriString* path);
|
||||
void nfc_make_app_folder(NfcApp* instance);
|
||||
|
||||
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);
|
||||
|
||||
void nfc_app_run_external(NfcApp* nfc, const char* app_path);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/hex.h>
|
||||
#include <toolbox/pipe.h>
|
||||
@@ -65,4 +64,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#include "../nfc_app_i.h"
|
||||
#include "loader/loader.h"
|
||||
|
||||
typedef enum {
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppMissing,
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppPresent,
|
||||
} NfcSceneMfClassicMfKeyCompleteState;
|
||||
|
||||
void nfc_scene_mf_classic_mfkey_complete_callback(
|
||||
GuiButtonType result,
|
||||
@@ -14,25 +20,48 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) {
|
||||
NfcApp* instance = context;
|
||||
|
||||
widget_add_string_element(
|
||||
instance->widget, 64, 2, AlignCenter, AlignTop, FontPrimary, "Completed!");
|
||||
widget_add_string_multiline_element(
|
||||
instance->widget,
|
||||
64,
|
||||
16,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools");
|
||||
widget_add_string_element(
|
||||
instance->widget, 29, 38, AlignLeft, AlignTop, FontSecondary, "or Apps > NFC > MFKey");
|
||||
widget_add_icon_element(instance->widget, 0, 39, &I_MFKey_qr_25x25);
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Finish",
|
||||
nfc_scene_mf_classic_mfkey_complete_callback,
|
||||
instance);
|
||||
instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Completed!");
|
||||
|
||||
NfcSceneMfClassicMfKeyCompleteState scene_state =
|
||||
storage_common_exists(instance->storage, NFC_MFKEY32_APP_PATH) ?
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppPresent :
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppMissing;
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneMfClassicMfkeyComplete, scene_state);
|
||||
|
||||
if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) {
|
||||
widget_add_string_multiline_element(
|
||||
instance->widget,
|
||||
64,
|
||||
13,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools");
|
||||
widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25);
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Finish",
|
||||
nfc_scene_mf_classic_mfkey_complete_callback,
|
||||
instance);
|
||||
} else {
|
||||
widget_add_string_multiline_element(
|
||||
instance->widget,
|
||||
60,
|
||||
16,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Now run Mfkey32\n to extract \nkeys");
|
||||
widget_add_icon_element(instance->widget, 5, 18, &I_WarningDolphin_45x42);
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Run",
|
||||
nfc_scene_mf_classic_mfkey_complete_callback,
|
||||
instance);
|
||||
}
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
@@ -42,8 +71,14 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneStart);
|
||||
NfcSceneMfClassicMfKeyCompleteState scene_state = scene_manager_get_scene_state(
|
||||
instance->scene_manager, NfcSceneMfClassicMfkeyComplete);
|
||||
if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneStart);
|
||||
} else {
|
||||
nfc_app_run_external(instance, NFC_MFKEY32_APP_PATH);
|
||||
}
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart};
|
||||
|
||||
@@ -29,7 +29,14 @@ bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
|
||||
consumed = true;
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
NfcSceneSaveConfirmState scene_state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm);
|
||||
|
||||
NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ?
|
||||
NfcSceneMfClassicMfkeyComplete :
|
||||
NfcSceneMfClassicDetectReader;
|
||||
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,13 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
NfcSceneSaveConfirmState scene_state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm);
|
||||
|
||||
NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ?
|
||||
NfcSceneMfClassicMfkeyComplete :
|
||||
NfcSceneMfClassicDetectReader;
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
consumed = true;
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
|
||||
consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/args.h>
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
@@ -64,4 +64,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/devices/devices.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
typedef struct SubGhzChatWorker SubGhzChatWorker;
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <lib/subghz/receiver.h>
|
||||
@@ -1182,4 +1183,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048);
|
||||
CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include <cli/cli.h>
|
||||
|
||||
void subghz_on_system_start(void);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include "bt_service/bt.h"
|
||||
@@ -229,4 +229,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(bt, execute, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(bt, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -4,17 +4,9 @@ App(
|
||||
entry_point="cli_on_system_start",
|
||||
cdefines=["SRV_CLI"],
|
||||
sources=[
|
||||
"cli.c",
|
||||
"shell/cli_shell.c",
|
||||
"shell/cli_shell_completions.c",
|
||||
"shell/cli_shell_line.c",
|
||||
"cli_commands.c",
|
||||
"cli_command_gpio.c",
|
||||
"cli_ansi.c",
|
||||
],
|
||||
sdk_headers=[
|
||||
"cli.h",
|
||||
"cli_ansi.h",
|
||||
"cli_main_commands.c",
|
||||
"cli_main_shell.c",
|
||||
],
|
||||
# This STARTUP has to be processed before those that depend on the "cli" record.
|
||||
# "cli" used to be a SERVICE, but it's been converted into a STARTUP in order to
|
||||
@@ -29,8 +21,8 @@ App(
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="cli_vcp_srv",
|
||||
stack_size=1024,
|
||||
order=40,
|
||||
sdk_headers=["cli_vcp.h"],
|
||||
order=10,
|
||||
sdk_headers=["cli_vcp.h", "cli.h"],
|
||||
sources=["cli_vcp.c"],
|
||||
)
|
||||
|
||||
@@ -50,13 +42,21 @@ App(
|
||||
sources=["commands/neofetch.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_subshell_demo",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_subshell_demo_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/subshell_demo.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_src",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_src_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -65,7 +65,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_uptime_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -74,7 +74,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_date_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -83,7 +83,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_sysctl_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -92,7 +92,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_top_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -101,7 +101,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_vibro_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -110,7 +110,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_led_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -119,7 +119,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_gpio_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c", "cli_command_gpio.c"],
|
||||
sources=["cli_main_commands.c", "cli_command_gpio.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -128,7 +128,7 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_i2c_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -137,5 +137,5 @@ App(
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_clear_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
sources=["cli_main_commands.c"],
|
||||
)
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
#include "cli.h"
|
||||
#include "cli_i.h"
|
||||
#include "cli_commands.h"
|
||||
#include "cli_ansi.h"
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#define TAG "cli"
|
||||
|
||||
struct Cli {
|
||||
CliCommandTree_t commands;
|
||||
FuriMutex* mutex;
|
||||
};
|
||||
|
||||
Cli* cli_alloc(void) {
|
||||
Cli* cli = malloc(sizeof(Cli));
|
||||
CliCommandTree_init(cli->commands);
|
||||
cli->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
||||
return cli;
|
||||
}
|
||||
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliExecuteCallback callback,
|
||||
void* context) {
|
||||
cli_add_command_ex(cli, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE);
|
||||
}
|
||||
|
||||
void cli_add_command_ex(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliExecuteCallback callback,
|
||||
void* context,
|
||||
size_t stack_size) {
|
||||
furi_check(cli);
|
||||
furi_check(name);
|
||||
furi_check(callback);
|
||||
|
||||
// the shell always attaches the pipe to the stdio, thus both flags can't be used at once
|
||||
if(flags & CliCommandFlagUseShellThread) furi_check(!(flags & CliCommandFlagDontAttachStdio));
|
||||
|
||||
FuriString* name_str;
|
||||
name_str = furi_string_alloc_set(name);
|
||||
// command cannot contain spaces
|
||||
furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE);
|
||||
|
||||
CliCommand command = {
|
||||
.context = context,
|
||||
.execute_callback = callback,
|
||||
.flags = flags,
|
||||
.stack_depth = stack_size,
|
||||
};
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_set_at(cli->commands, name_str, command);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
furi_string_free(name_str);
|
||||
}
|
||||
|
||||
void cli_delete_command(Cli* cli, const char* name) {
|
||||
furi_check(cli);
|
||||
FuriString* name_str;
|
||||
name_str = furi_string_alloc_set(name);
|
||||
furi_string_trim(name_str);
|
||||
|
||||
size_t name_replace;
|
||||
do {
|
||||
name_replace = furi_string_replace(name_str, " ", "_");
|
||||
} while(name_replace != FURI_STRING_FAILURE);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_erase(cli->commands, name_str);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
furi_string_free(name_str);
|
||||
}
|
||||
|
||||
bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) {
|
||||
furi_assert(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommand* data = CliCommandTree_get(cli->commands, command);
|
||||
if(data) *result = *data;
|
||||
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
return !!data;
|
||||
}
|
||||
|
||||
void cli_remove_external_commands(Cli* cli) {
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
// FIXME FL-3977: memory leak somewhere within this function
|
||||
|
||||
CliCommandTree_t internal_cmds;
|
||||
CliCommandTree_init(internal_cmds);
|
||||
for
|
||||
M_EACH(item, cli->commands, CliCommandTree_t) {
|
||||
if(!(item->value_ptr->flags & CliCommandFlagExternal))
|
||||
CliCommandTree_set_at(internal_cmds, *item->key_ptr, *item->value_ptr);
|
||||
}
|
||||
CliCommandTree_move(cli->commands, internal_cmds);
|
||||
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void cli_enumerate_external_commands(Cli* cli) {
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
FURI_LOG_D(TAG, "Enumerating external commands");
|
||||
|
||||
cli_remove_external_commands(cli);
|
||||
|
||||
// iterate over files in plugin directory
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* plugin_dir = storage_file_alloc(storage);
|
||||
|
||||
if(storage_dir_open(plugin_dir, CLI_COMMANDS_PATH)) {
|
||||
char plugin_filename[64];
|
||||
FuriString* plugin_name = furi_string_alloc();
|
||||
|
||||
while(storage_dir_read(plugin_dir, NULL, plugin_filename, sizeof(plugin_filename))) {
|
||||
FURI_LOG_T(TAG, "Plugin: %s", plugin_filename);
|
||||
furi_string_set_str(plugin_name, plugin_filename);
|
||||
furi_string_replace_all_str(plugin_name, ".fal", "");
|
||||
furi_string_replace_at(plugin_name, 0, 4, ""); // remove "cli_" in the beginning
|
||||
CliCommand command = {
|
||||
.context = NULL,
|
||||
.execute_callback = NULL,
|
||||
.flags = CliCommandFlagExternal,
|
||||
};
|
||||
CliCommandTree_set_at(cli->commands, plugin_name, command);
|
||||
}
|
||||
|
||||
furi_string_free(plugin_name);
|
||||
}
|
||||
|
||||
storage_file_free(plugin_dir);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
FURI_LOG_D(TAG, "Finished enumerating external commands");
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void cli_lock_commands(Cli* cli) {
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void cli_unlock_commands(Cli* cli) {
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
CliCommandTree_t* cli_get_commands(Cli* cli) {
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_get_owner(cli->mutex) == furi_thread_get_current_id());
|
||||
return &cli->commands;
|
||||
}
|
||||
|
||||
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side) {
|
||||
if(pipe_state(side) == PipeStateBroken) return true;
|
||||
if(!pipe_bytes_available(side)) return false;
|
||||
char c = getchar();
|
||||
return c == CliKeyETX;
|
||||
}
|
||||
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
||||
furi_check(cmd);
|
||||
furi_check(arg);
|
||||
furi_check(usage);
|
||||
|
||||
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
|
||||
}
|
||||
|
||||
void cli_on_system_start(void) {
|
||||
Cli* cli = cli_alloc();
|
||||
cli_commands_init(cli);
|
||||
cli_enumerate_external_commands(cli);
|
||||
furi_record_create(RECORD_CLI, cli);
|
||||
}
|
||||
@@ -1,131 +1,13 @@
|
||||
/**
|
||||
* @file cli.h
|
||||
* API for registering commands with the CLI
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <m-array.h>
|
||||
#include "cli_ansi.h"
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Compatibility header for ease of porting existing apps.
|
||||
* In short:
|
||||
* Cli* is replaced with with CliRegistry*
|
||||
* cli_* functions are replaced with cli_registry_* functions
|
||||
* (i.e., cli_add_command() is now cli_registry_add_command())
|
||||
*/
|
||||
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
|
||||
#define RECORD_CLI "cli"
|
||||
|
||||
typedef enum {
|
||||
CliCommandFlagDefault = 0, /**< Default */
|
||||
CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */
|
||||
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
||||
CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */
|
||||
CliCommandFlagUseShellThread =
|
||||
(1
|
||||
<< 3), /**< Don't start a separate thread to run the command in. Incompatible with DontAttachStdio */
|
||||
|
||||
// internal flags (do not set them yourselves!)
|
||||
|
||||
CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */
|
||||
} CliCommandFlag;
|
||||
|
||||
/** Cli type anonymous structure */
|
||||
typedef struct Cli Cli;
|
||||
|
||||
/**
|
||||
* @brief CLI execution callback pointer. Implement this interface and use
|
||||
* `add_cli_command`.
|
||||
*
|
||||
* This callback will be called from a separate thread spawned just for your
|
||||
* command. The pipe will be installed as the thread's stdio, so you can use
|
||||
* `printf`, `getchar` and other standard functions to communicate with the
|
||||
* user.
|
||||
*
|
||||
* @param [in] pipe Pipe that can be used to send and receive data. If
|
||||
* `CliCommandFlagDontAttachStdio` was not set, you can
|
||||
* also use standard C functions (printf, getc, etc.) to
|
||||
* access this pipe.
|
||||
* @param [in] args String with what was passed after the command
|
||||
* @param [in] context Whatever you provided to `cli_add_command`
|
||||
*/
|
||||
typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context);
|
||||
|
||||
/**
|
||||
* @brief Registers a command with the CLI. Provides less options than the `_ex`
|
||||
* counterpart.
|
||||
*
|
||||
* @param [in] cli Pointer to CLI instance
|
||||
* @param [in] name Command name
|
||||
* @param [in] flags CliCommandFlag
|
||||
* @param [in] callback Callback function
|
||||
* @param [in] context Custom context
|
||||
*/
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliExecuteCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* @brief Registers a command with the CLI. Provides more options than the
|
||||
* non-`_ex` counterpart.
|
||||
*
|
||||
* @param [in] cli Pointer to CLI instance
|
||||
* @param [in] name Command name
|
||||
* @param [in] flags CliCommandFlag
|
||||
* @param [in] callback Callback function
|
||||
* @param [in] context Custom context
|
||||
* @param [in] stack_size Thread stack size
|
||||
*/
|
||||
void cli_add_command_ex(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliExecuteCallback callback,
|
||||
void* context,
|
||||
size_t stack_size);
|
||||
|
||||
/**
|
||||
* @brief Deletes a cli command
|
||||
*
|
||||
* @param [in] cli pointer to cli instance
|
||||
* @param [in] name command name
|
||||
*/
|
||||
void cli_delete_command(Cli* cli, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Unregisters all external commands
|
||||
*
|
||||
* @param [in] cli pointer to the cli instance
|
||||
*/
|
||||
void cli_remove_external_commands(Cli* cli);
|
||||
|
||||
/**
|
||||
* @brief Reloads the list of externally available commands
|
||||
*
|
||||
* @param [in] cli pointer to cli instance
|
||||
*/
|
||||
void cli_enumerate_external_commands(Cli* cli);
|
||||
|
||||
/**
|
||||
* @brief Detects if Ctrl+C has been pressed or session has been terminated
|
||||
*
|
||||
* @param [in] side Pointer to pipe side given to the command thread
|
||||
* @warning This function also assumes that the pipe is installed as the
|
||||
* thread's stdio
|
||||
* @warning This function will consume 1 byte from the pipe
|
||||
*/
|
||||
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side);
|
||||
|
||||
/** Print unified cmd usage tip
|
||||
*
|
||||
* @param cmd cmd name
|
||||
* @param usage usage tip
|
||||
* @param arg arg passed by user
|
||||
*/
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <furi_hal.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
|
||||
void cli_command_gpio_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_i.h"
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
void cli_commands_init(Cli* cli);
|
||||
|
||||
#define PLUGIN_APP_ID "cli"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
CliExecuteCallback execute_callback;
|
||||
CliCommandFlag flags;
|
||||
size_t stack_depth;
|
||||
} CliCommandDescriptor;
|
||||
|
||||
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth) \
|
||||
static const CliCommandDescriptor cli_##name##_desc = { \
|
||||
#name, \
|
||||
&execute_callback, \
|
||||
flags, \
|
||||
stack_depth, \
|
||||
}; \
|
||||
\
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor_##name = { \
|
||||
.appid = PLUGIN_APP_ID, \
|
||||
.ep_api_version = PLUGIN_API_VERSION, \
|
||||
.entry_point = &cli_##name##_desc, \
|
||||
}; \
|
||||
\
|
||||
const FlipperAppPluginDescriptor* cli_##name##_ep(void) { \
|
||||
return &plugin_descriptor_##name; \
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* @file cli_i.h
|
||||
* Internal API for getting commands registered with the CLI
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <m-bptree.h>
|
||||
#include "cli.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U)
|
||||
#define CLI_COMMANDS_PATH "/ext/apps_data/cli/plugins"
|
||||
|
||||
typedef struct {
|
||||
void* context; //<! Context passed to callbacks
|
||||
CliExecuteCallback execute_callback; //<! Callback for command execution
|
||||
CliCommandFlag flags;
|
||||
size_t stack_depth;
|
||||
} CliCommand;
|
||||
|
||||
#define CLI_COMMANDS_TREE_RANK 4
|
||||
|
||||
// -V:BPTREE_DEF2:1103
|
||||
// -V:BPTREE_DEF2:524
|
||||
BPTREE_DEF2(
|
||||
CliCommandTree,
|
||||
CLI_COMMANDS_TREE_RANK,
|
||||
FuriString*,
|
||||
FURI_STRING_OPLIST,
|
||||
CliCommand,
|
||||
M_POD_OPLIST);
|
||||
|
||||
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST2(CliCommandTree, FURI_STRING_OPLIST, M_POD_OPLIST)
|
||||
|
||||
bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result);
|
||||
|
||||
void cli_lock_commands(Cli* cli);
|
||||
|
||||
void cli_unlock_commands(Cli* cli);
|
||||
|
||||
/**
|
||||
* @warning Surround calls to this function with `cli_[un]lock_commands`
|
||||
*/
|
||||
CliCommandTree_t* cli_get_commands(Cli* cli);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "cli_commands.h"
|
||||
#include "cli_main_commands.h"
|
||||
#include "cli_command_gpio.h"
|
||||
#include "cli_ansi.h"
|
||||
#include "cli.h"
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
|
||||
#include <core/thread.h>
|
||||
#include <furi_hal.h>
|
||||
@@ -56,49 +55,6 @@ void cli_command_info(PipeSide* pipe, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("Available commands:" ANSI_FG_GREEN);
|
||||
|
||||
// count non-hidden commands
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_lock_commands(cli);
|
||||
CliCommandTree_t* commands = cli_get_commands(cli);
|
||||
size_t commands_count = CliCommandTree_size(*commands);
|
||||
|
||||
// create iterators starting at different positions
|
||||
const size_t columns = 3;
|
||||
const size_t commands_per_column = (commands_count / columns) + (commands_count % columns);
|
||||
CliCommandTree_it_t iterators[columns];
|
||||
for(size_t c = 0; c < columns; c++) {
|
||||
CliCommandTree_it(iterators[c], *commands);
|
||||
for(size_t i = 0; i < c * commands_per_column; i++)
|
||||
CliCommandTree_next(iterators[c]);
|
||||
}
|
||||
|
||||
// print commands
|
||||
for(size_t r = 0; r < commands_per_column; r++) {
|
||||
printf("\r\n");
|
||||
|
||||
for(size_t c = 0; c < columns; c++) {
|
||||
if(!CliCommandTree_end_p(iterators[c])) {
|
||||
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]);
|
||||
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
|
||||
CliCommandTree_next(iterators[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf(ANSI_RESET
|
||||
"\r\nIf you just added a new command and can't see it above, run `reload_ext_cmds`");
|
||||
printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli");
|
||||
|
||||
cli_unlock_commands(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
@@ -567,16 +523,6 @@ void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_enumerate_external_commands(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
printf("OK!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
|
||||
*/
|
||||
@@ -605,29 +551,33 @@ void cli_command_clear(PipeSide* pipe, FuriString* args, void* context) {
|
||||
printf("\e[2J\e[H");
|
||||
}
|
||||
|
||||
void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_add_command(
|
||||
cli, "reload_ext_cmds", CliCommandFlagDefault, cli_command_reload_external, NULL);
|
||||
void cli_main_commands_init(CliRegistry* registry) {
|
||||
cli_registry_add_command(
|
||||
registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_registry_add_command(registry, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
||||
cli_registry_add_command(
|
||||
registry, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
|
||||
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
cli_add_command(cli, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
|
||||
cli_registry_add_command(registry, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_registry_add_command(
|
||||
registry, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(src, cli_command_src, CliCommandFlagParallelSafe, 768);
|
||||
CLI_COMMAND_INTERFACE(uptime, cli_command_uptime, CliCommandFlagDefault, 768);
|
||||
CLI_COMMAND_INTERFACE(date, cli_command_date, CliCommandFlagParallelSafe, 768);
|
||||
CLI_COMMAND_INTERFACE(sysctl, cli_command_sysctl, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(top, cli_command_top, CliCommandFlagParallelSafe, 1024);
|
||||
CLI_COMMAND_INTERFACE(vibro, cli_command_vibro, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(led, cli_command_led, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(gpio, cli_command_gpio, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(i2c, cli_command_i2c, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(clear, cli_command_clear, CliCommandFlagParallelSafe, 768);
|
||||
CLI_COMMAND_INTERFACE(src, cli_command_src, CliCommandFlagParallelSafe, 768, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(uptime, cli_command_uptime, CliCommandFlagDefault, 768, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(date, cli_command_date, CliCommandFlagParallelSafe, 768, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(sysctl, cli_command_sysctl, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(top, cli_command_top, CliCommandFlagParallelSafe, 1024, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(vibro, cli_command_vibro, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(led, cli_command_led, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(gpio, cli_command_gpio, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(i2c, cli_command_i2c, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
CLI_COMMAND_INTERFACE(clear, cli_command_clear, CliCommandFlagParallelSafe, 768, CLI_APPID);
|
||||
|
||||
void cli_on_system_start(void) {
|
||||
CliRegistry* registry = cli_registry_alloc();
|
||||
cli_main_commands_init(registry);
|
||||
furi_record_create(RECORD_CLI, registry);
|
||||
}
|
||||
9
applications/services/cli/cli_main_commands.h
Normal file
9
applications/services/cli/cli_main_commands.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli.h"
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
|
||||
#define CLI_APPID "cli"
|
||||
|
||||
void cli_main_commands_init(CliRegistry* registry);
|
||||
46
applications/services/cli/cli_main_shell.c
Normal file
46
applications/services/cli/cli_main_shell.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "cli_main_shell.h"
|
||||
#include "cli_main_commands.h"
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
#include <toolbox/cli/shell/cli_shell.h>
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
void cli_main_motd(void* context) {
|
||||
UNUSED(context);
|
||||
printf(ANSI_FLIPPER_BRAND_ORANGE
|
||||
"\r\n"
|
||||
" _.-------.._ -,\r\n"
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
|
||||
" | | | 0 | | .-' ,/` /\r\n"
|
||||
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
|
||||
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
|
||||
" | `-...| -.___-Z:_______J...---;\r\n"
|
||||
" : ` _-'\r\n"
|
||||
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
|
||||
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
|
||||
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
|
||||
"\r\n" ANSI_FG_BR_WHITE "Welcome to Flipper Zero Command Line Interface!\r\n"
|
||||
"Read the manual: https://docs.flipper.net/development/cli\r\n"
|
||||
"Run `help` or `?` to list available commands\r\n"
|
||||
"\r\n" ANSI_RESET);
|
||||
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
printf(
|
||||
"Firmware version: %s %s (%s%s built on %s)\r\n",
|
||||
version_get_gitbranch(firmware_version),
|
||||
version_get_version(firmware_version),
|
||||
version_get_githash(firmware_version),
|
||||
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
|
||||
version_get_builddate(firmware_version));
|
||||
}
|
||||
}
|
||||
|
||||
const CliCommandExternalConfig cli_main_ext_config = {
|
||||
.search_directory = "/ext/apps_data/cli/plugins",
|
||||
.fal_prefix = "cli_",
|
||||
.appid = CLI_APPID,
|
||||
};
|
||||
7
applications/services/cli/cli_main_shell.h
Normal file
7
applications/services/cli/cli_main_shell.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
|
||||
void cli_main_motd(void* context);
|
||||
|
||||
extern const CliCommandExternalConfig cli_main_ext_config;
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "cli_vcp.h"
|
||||
#include "shell/cli_shell.h"
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/shell/cli_shell.h>
|
||||
#include "cli_main_shell.h"
|
||||
#include "cli_main_commands.h"
|
||||
|
||||
#define TAG "CliVcp"
|
||||
|
||||
@@ -28,29 +30,27 @@ typedef struct {
|
||||
} CliVcpMessage;
|
||||
|
||||
typedef enum {
|
||||
CliVcpInternalEventConnected = (1 << 0),
|
||||
CliVcpInternalEventDisconnected = (1 << 1),
|
||||
CliVcpInternalEventTxDone = (1 << 2),
|
||||
CliVcpInternalEventRx = (1 << 3),
|
||||
CliVcpInternalEventConnected,
|
||||
CliVcpInternalEventDisconnected,
|
||||
CliVcpInternalEventTxDone,
|
||||
CliVcpInternalEventRx,
|
||||
} CliVcpInternalEvent;
|
||||
|
||||
#define CliVcpInternalEventAll \
|
||||
(CliVcpInternalEventConnected | CliVcpInternalEventDisconnected | CliVcpInternalEventTxDone | \
|
||||
CliVcpInternalEventRx)
|
||||
|
||||
struct CliVcp {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* message_queue; // <! external messages
|
||||
FuriThreadId thread_id;
|
||||
FuriMessageQueue* internal_evt_queue;
|
||||
|
||||
bool is_enabled, is_connected;
|
||||
FuriHalUsbInterface* previous_interface;
|
||||
|
||||
PipeSide* own_pipe;
|
||||
bool is_currently_transmitting;
|
||||
PipeSide* shell_pipe;
|
||||
volatile bool is_currently_transmitting;
|
||||
size_t previous_tx_length;
|
||||
|
||||
FuriThread* shell;
|
||||
CliRegistry* main_registry;
|
||||
CliShell* shell;
|
||||
};
|
||||
|
||||
// ============
|
||||
@@ -97,11 +97,12 @@ static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) {
|
||||
// =============
|
||||
|
||||
static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) {
|
||||
furi_thread_flags_set(cli_vcp->thread_id, event);
|
||||
furi_check(furi_message_queue_put(cli_vcp->internal_evt_queue, &event, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static void cli_vcp_cdc_tx_done(void* context) {
|
||||
CliVcp* cli_vcp = context;
|
||||
cli_vcp->is_currently_transmitting = false;
|
||||
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone);
|
||||
}
|
||||
|
||||
@@ -186,12 +187,25 @@ static void cli_vcp_message_received(FuriEventLoopObject* object, void* context)
|
||||
/**
|
||||
* Processes messages arriving from CDC event callbacks
|
||||
*/
|
||||
static void cli_vcp_internal_event_happened(void* context) {
|
||||
static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* context) {
|
||||
CliVcp* cli_vcp = context;
|
||||
CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0);
|
||||
furi_check(!(event & FuriFlagError));
|
||||
CliVcpInternalEvent event;
|
||||
furi_check(furi_message_queue_get(object, &event, 0) == FuriStatusOk);
|
||||
|
||||
if(event & CliVcpInternalEventDisconnected) {
|
||||
switch(event) {
|
||||
case CliVcpInternalEventRx: {
|
||||
VCP_TRACE(TAG, "Rx");
|
||||
cli_vcp_maybe_receive_data(cli_vcp);
|
||||
break;
|
||||
}
|
||||
|
||||
case CliVcpInternalEventTxDone: {
|
||||
VCP_TRACE(TAG, "TxDone");
|
||||
cli_vcp_maybe_send_data(cli_vcp);
|
||||
break;
|
||||
}
|
||||
|
||||
case CliVcpInternalEventDisconnected: {
|
||||
if(!cli_vcp->is_connected) return;
|
||||
FURI_LOG_D(TAG, "Disconnected");
|
||||
cli_vcp->is_connected = false;
|
||||
@@ -200,9 +214,10 @@ static void cli_vcp_internal_event_happened(void* context) {
|
||||
pipe_detach_from_event_loop(cli_vcp->own_pipe);
|
||||
pipe_free(cli_vcp->own_pipe);
|
||||
cli_vcp->own_pipe = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if(event & CliVcpInternalEventConnected) {
|
||||
case CliVcpInternalEventConnected: {
|
||||
if(cli_vcp->is_connected) return;
|
||||
FURI_LOG_D(TAG, "Connected");
|
||||
cli_vcp->is_connected = true;
|
||||
@@ -210,13 +225,15 @@ static void cli_vcp_internal_event_happened(void* context) {
|
||||
// wait for previous shell to stop
|
||||
furi_check(!cli_vcp->own_pipe);
|
||||
if(cli_vcp->shell) {
|
||||
furi_thread_join(cli_vcp->shell);
|
||||
furi_thread_free(cli_vcp->shell);
|
||||
cli_shell_join(cli_vcp->shell);
|
||||
cli_shell_free(cli_vcp->shell);
|
||||
pipe_free(cli_vcp->shell_pipe);
|
||||
}
|
||||
|
||||
// start shell thread
|
||||
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
|
||||
cli_vcp->own_pipe = bundle.alices_side;
|
||||
cli_vcp->shell_pipe = bundle.bobs_side;
|
||||
pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop);
|
||||
pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp);
|
||||
pipe_set_data_arrived_callback(
|
||||
@@ -224,18 +241,11 @@ static void cli_vcp_internal_event_happened(void* context) {
|
||||
pipe_set_space_freed_callback(
|
||||
cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge);
|
||||
furi_delay_ms(33); // we are too fast, minicom isn't ready yet
|
||||
cli_vcp->shell = cli_shell_start(bundle.bobs_side);
|
||||
cli_vcp->shell = cli_shell_alloc(
|
||||
cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config);
|
||||
cli_shell_start(cli_vcp->shell);
|
||||
break;
|
||||
}
|
||||
|
||||
if(event & CliVcpInternalEventRx) {
|
||||
VCP_TRACE(TAG, "Rx");
|
||||
cli_vcp_maybe_receive_data(cli_vcp);
|
||||
}
|
||||
|
||||
if(event & CliVcpInternalEventTxDone) {
|
||||
VCP_TRACE(TAG, "TxDone");
|
||||
cli_vcp->is_currently_transmitting = false;
|
||||
cli_vcp_maybe_send_data(cli_vcp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +255,6 @@ static void cli_vcp_internal_event_happened(void* context) {
|
||||
|
||||
static CliVcp* cli_vcp_alloc(void) {
|
||||
CliVcp* cli_vcp = malloc(sizeof(CliVcp));
|
||||
cli_vcp->thread_id = furi_thread_get_current_id();
|
||||
|
||||
cli_vcp->event_loop = furi_event_loop_alloc();
|
||||
|
||||
@@ -257,8 +266,16 @@ static CliVcp* cli_vcp_alloc(void) {
|
||||
cli_vcp_message_received,
|
||||
cli_vcp);
|
||||
|
||||
furi_event_loop_subscribe_thread_flags(
|
||||
cli_vcp->event_loop, cli_vcp_internal_event_happened, cli_vcp);
|
||||
cli_vcp->internal_evt_queue =
|
||||
furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpInternalEvent));
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
cli_vcp->event_loop,
|
||||
cli_vcp->internal_evt_queue,
|
||||
FuriEventLoopEventIn,
|
||||
cli_vcp_internal_event_happened,
|
||||
cli_vcp);
|
||||
|
||||
cli_vcp->main_registry = furi_record_open(RECORD_CLI);
|
||||
|
||||
return cli_vcp;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../cli_commands.h"
|
||||
#include "../cli_main_commands.h"
|
||||
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
@@ -7,4 +7,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
puts("Hello, World!");
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagDefault, 768);
|
||||
CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagParallelSafe, 768, CLI_APPID);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "../cli_commands.h"
|
||||
#include "../cli_main_commands.h"
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
#include <toolbox/version.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_info.h>
|
||||
@@ -156,4 +157,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
#undef NEOFETCH_COLOR
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagDefault, 2048);
|
||||
CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID);
|
||||
|
||||
43
applications/services/cli/commands/subshell_demo.c
Normal file
43
applications/services/cli/commands/subshell_demo.c
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "../cli_main_commands.h"
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <toolbox/cli/shell/cli_shell.h>
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
|
||||
#define RAINBOW_SUBCOMMAND \
|
||||
ANSI_FG_RED "s" ANSI_FG_YELLOW "u" ANSI_FG_BLUE "b" ANSI_FG_GREEN "c" ANSI_FG_MAGENTA \
|
||||
"o" ANSI_FG_RED "m" ANSI_FG_YELLOW "m" ANSI_FG_BLUE "a" ANSI_FG_GREEN \
|
||||
"n" ANSI_FG_MAGENTA "d"
|
||||
|
||||
static void subcommand(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("This is a ✨" RAINBOW_SUBCOMMAND ANSI_RESET "✨!");
|
||||
}
|
||||
|
||||
static void motd(void* context) {
|
||||
UNUSED(context);
|
||||
printf("\r\n");
|
||||
printf("+------------------------------------+\r\n");
|
||||
printf("| Hello world! |\r\n");
|
||||
printf("| This is the " ANSI_FG_GREEN "MOTD" ANSI_RESET " for our " ANSI_FG_BLUE
|
||||
"subshell" ANSI_RESET "! |\r\n");
|
||||
printf("+------------------------------------+\r\n");
|
||||
}
|
||||
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
CliRegistry* registry = cli_registry_alloc();
|
||||
cli_registry_add_command(registry, "subcommand", CliCommandFlagParallelSafe, subcommand, NULL);
|
||||
|
||||
CliShell* shell = cli_shell_alloc(motd, NULL, pipe, registry, NULL);
|
||||
cli_shell_set_prompt(shell, "subshell");
|
||||
cli_shell_start(shell);
|
||||
cli_shell_join(shell);
|
||||
|
||||
cli_shell_free(shell);
|
||||
cli_registry_free(registry);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(subshell_demo, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID);
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CLI_SHELL_STACK_SIZE (4 * 1024U)
|
||||
|
||||
FuriThread* cli_shell_start(PipeSide* pipe);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
|
||||
void crypto_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
@@ -318,4 +319,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(crypto, execute, CliCommandFlagDefault, 1024);
|
||||
CLI_COMMAND_INTERFACE(crypto, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "desktop_i.h"
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_vcp.h>
|
||||
|
||||
#include <gui/gui_i.h>
|
||||
@@ -29,9 +28,7 @@ static void desktop_loader_callback(const void* message, void* context) {
|
||||
if(event->type == LoaderEventTypeApplicationBeforeLoad) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted);
|
||||
furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk);
|
||||
} else if(
|
||||
event->type == LoaderEventTypeApplicationLoadFailed ||
|
||||
event->type == LoaderEventTypeApplicationStopped) {
|
||||
} else if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) {
|
||||
} else if(furi_string_equal(keybind, "Lock with PIN")) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventLockWithPin);
|
||||
} else if(furi_string_equal(keybind, "Wipe Device")) {
|
||||
loader_start_detached_with_gui_error(desktop->loader, "Storage", "wipe");
|
||||
loader_start_detached_with_gui_error(desktop->loader, "Storage", "Wipe Device");
|
||||
} else {
|
||||
if(storage_common_exists(desktop->storage, furi_string_get_cstr(keybind))) {
|
||||
run_with_default_app(furi_string_get_cstr(keybind));
|
||||
|
||||
@@ -8,5 +8,5 @@ App(
|
||||
],
|
||||
requires=["rpc_start"],
|
||||
provides=["expansion_settings"],
|
||||
order=150,
|
||||
order=100,
|
||||
)
|
||||
|
||||
@@ -14,8 +14,12 @@ struct Submenu {
|
||||
typedef struct {
|
||||
FuriString* label;
|
||||
uint32_t index;
|
||||
SubmenuItemCallback callback;
|
||||
union {
|
||||
SubmenuItemCallback callback;
|
||||
SubmenuItemCallbackEx callback_ex;
|
||||
};
|
||||
void* callback_context;
|
||||
bool has_extended_events;
|
||||
|
||||
bool locked;
|
||||
FuriString* locked_message;
|
||||
@@ -73,7 +77,7 @@ typedef struct {
|
||||
|
||||
static void submenu_process_up(Submenu* submenu);
|
||||
static void submenu_process_down(Submenu* submenu);
|
||||
static void submenu_process_ok(Submenu* submenu);
|
||||
static void submenu_process_ok(Submenu* submenu, InputType input_type);
|
||||
|
||||
static size_t submenu_items_on_screen(SubmenuModel* model) {
|
||||
size_t res = (model->is_vertical) ? 8 : 4;
|
||||
@@ -181,11 +185,13 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) {
|
||||
{ locked_message_visible = model->locked_message_visible; },
|
||||
false);
|
||||
|
||||
if((event->type != InputTypePress && event->type != InputTypeRelease) &&
|
||||
locked_message_visible) {
|
||||
if(locked_message_visible && (event->type == InputTypeShort || event->type == InputTypeLong)) {
|
||||
with_view_model(
|
||||
submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
consumed = true;
|
||||
submenu_process_ok(submenu, event->type);
|
||||
} else if(event->type == InputTypeShort) {
|
||||
switch(event->key) {
|
||||
case InputKeyUp:
|
||||
@@ -196,10 +202,6 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) {
|
||||
consumed = true;
|
||||
submenu_process_down(submenu);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
consumed = true;
|
||||
submenu_process_ok(submenu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -303,6 +305,7 @@ void submenu_add_lockable_item(
|
||||
item->index = index;
|
||||
item->callback = callback;
|
||||
item->callback_context = callback_context;
|
||||
item->has_extended_events = false;
|
||||
item->locked = locked;
|
||||
if(locked) {
|
||||
furi_string_set_str(item->locked_message, locked_message);
|
||||
@@ -311,6 +314,30 @@ void submenu_add_lockable_item(
|
||||
true);
|
||||
}
|
||||
|
||||
void submenu_add_item_ex(
|
||||
Submenu* submenu,
|
||||
const char* label,
|
||||
uint32_t index,
|
||||
SubmenuItemCallbackEx callback,
|
||||
void* callback_context) {
|
||||
SubmenuItem* item = NULL;
|
||||
furi_check(label);
|
||||
furi_check(submenu);
|
||||
|
||||
with_view_model(
|
||||
submenu->view,
|
||||
SubmenuModel * model,
|
||||
{
|
||||
item = SubmenuItemArray_push_new(model->items);
|
||||
furi_string_set_str(item->label, label);
|
||||
item->index = index;
|
||||
item->callback_ex = callback;
|
||||
item->callback_context = callback_context;
|
||||
item->has_extended_events = true;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) {
|
||||
furi_check(submenu);
|
||||
furi_check(label);
|
||||
@@ -455,7 +482,7 @@ void submenu_process_down(Submenu* submenu) {
|
||||
true);
|
||||
}
|
||||
|
||||
void submenu_process_ok(Submenu* submenu) {
|
||||
void submenu_process_ok(Submenu* submenu, InputType input_type) {
|
||||
SubmenuItem* item = NULL;
|
||||
|
||||
with_view_model(
|
||||
@@ -466,15 +493,20 @@ void submenu_process_ok(Submenu* submenu) {
|
||||
if(model->position < items_size) {
|
||||
item = SubmenuItemArray_get(model->items, model->position);
|
||||
}
|
||||
if(item && item->locked) {
|
||||
if(item && item->locked &&
|
||||
(input_type == InputTypeShort || input_type == InputTypeLong)) {
|
||||
model->locked_message_visible = true;
|
||||
furi_timer_start(submenu->locked_timer, furi_kernel_get_tick_frequency() * 3);
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
if(item && !item->locked && item->callback) {
|
||||
if(!item || item->locked) return;
|
||||
|
||||
if(!item->has_extended_events && input_type == InputTypeShort && item->callback) {
|
||||
item->callback(item->callback_context, item->index);
|
||||
} else if(item->has_extended_events && item->callback_ex) {
|
||||
item->callback_ex(item->callback_context, input_type, item->index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ extern "C" {
|
||||
/** Submenu anonymous structure */
|
||||
typedef struct Submenu Submenu;
|
||||
typedef void (*SubmenuItemCallback)(void* context, uint32_t index);
|
||||
typedef void (*SubmenuItemCallbackEx)(void* context, InputType input_type, uint32_t index);
|
||||
|
||||
/** Allocate and initialize submenu
|
||||
*
|
||||
@@ -53,6 +54,22 @@ void submenu_add_item(
|
||||
SubmenuItemCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
/** Add item to submenu with extended press events
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
* @param label menu item label
|
||||
* @param index menu item index, used for callback, may be
|
||||
* the same with other items
|
||||
* @param callback menu item extended callback
|
||||
* @param callback_context menu item callback context
|
||||
*/
|
||||
void submenu_add_item_ex(
|
||||
Submenu* submenu,
|
||||
const char* label,
|
||||
uint32_t index,
|
||||
SubmenuItemCallbackEx callback,
|
||||
void* callback_context);
|
||||
|
||||
/** Add lockable item to submenu
|
||||
*
|
||||
* @param submenu Submenu instance
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <furi_hal_vibro.h>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "input.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
@@ -227,4 +228,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_INPUT_EVENTS);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(input, execute, CliCommandFlagParallelSafe, 1024);
|
||||
CLI_COMMAND_INTERFACE(input, execute, CliCommandFlagParallelSafe, 1024, CLI_APPID);
|
||||
|
||||
@@ -175,6 +175,13 @@ static void loader_show_gui_error(
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
}
|
||||
|
||||
static void loader_generic_synchronous_request(Loader* loader, LoaderMessage* message) {
|
||||
furi_check(loader);
|
||||
message->api_lock = api_lock_alloc_locked();
|
||||
furi_message_queue_put(loader->queue, message, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message->api_lock);
|
||||
}
|
||||
|
||||
LoaderStatus
|
||||
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
|
||||
furi_check(loader);
|
||||
@@ -210,16 +217,12 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons
|
||||
}
|
||||
|
||||
bool loader_lock(Loader* loader) {
|
||||
furi_check(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);
|
||||
|
||||
LoaderMessage message = {
|
||||
.type = LoaderMessageTypeLock,
|
||||
.bool_value = &result,
|
||||
};
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
@@ -233,16 +236,12 @@ void loader_unlock(Loader* loader) {
|
||||
}
|
||||
|
||||
bool loader_is_locked(Loader* loader) {
|
||||
furi_check(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);
|
||||
|
||||
LoaderMessage message = {
|
||||
.type = LoaderMessageTypeIsLocked,
|
||||
.bool_value = &result,
|
||||
};
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
@@ -273,42 +272,63 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
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);
|
||||
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
bool loader_get_application_launch_path(Loader* loader, FuriString* name) {
|
||||
LoaderMessageBoolResult result;
|
||||
LoaderMessage message = {
|
||||
.type = LoaderMessageTypeGetApplicationLaunchPath,
|
||||
.application_name = name,
|
||||
.bool_value = &result,
|
||||
};
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
return result.value;
|
||||
}
|
||||
|
||||
void loader_enqueue_launch(
|
||||
Loader* loader,
|
||||
const char* name,
|
||||
const char* args,
|
||||
LoaderDeferredLaunchFlag flags) {
|
||||
LoaderMessage message = {
|
||||
.type = LoaderMessageTypeEnqueueLaunch,
|
||||
.defer_start =
|
||||
{
|
||||
.name_or_path = strdup(name),
|
||||
.args = args ? strdup(args) : NULL,
|
||||
.flags = flags,
|
||||
},
|
||||
};
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
}
|
||||
|
||||
void loader_clear_launch_queue(Loader* loader) {
|
||||
LoaderMessage message = {
|
||||
.type = LoaderMessageTypeClearLaunchQueue,
|
||||
};
|
||||
loader_generic_synchronous_request(loader, &message);
|
||||
}
|
||||
|
||||
// callbacks
|
||||
|
||||
static void loader_menu_closed_callback(void* context) {
|
||||
@@ -345,12 +365,10 @@ static Loader* loader_alloc(void) {
|
||||
Loader* loader = malloc(sizeof(Loader));
|
||||
loader->pubsub = furi_pubsub_alloc();
|
||||
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
|
||||
loader->loader_menu = NULL;
|
||||
loader->loader_applications = NULL;
|
||||
loader->app.args = NULL;
|
||||
loader->app.thread = NULL;
|
||||
loader->app.insomniac = false;
|
||||
loader->app.fap = NULL;
|
||||
loader->gui = furi_record_open(RECORD_GUI);
|
||||
loader->view_holder = view_holder_alloc();
|
||||
loader->loading = loading_alloc();
|
||||
view_holder_attach_to_gui(loader->view_holder, loader->gui);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@@ -417,9 +435,6 @@ static void loader_start_internal_app(
|
||||
const FlipperInternalApplication* app,
|
||||
const char* args) {
|
||||
FURI_LOG_I(TAG, "Starting %s", app->name);
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationBeforeLoad;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
if(app->flags & FlipperApplicationFlagUnloadAssetPacks) {
|
||||
loader->app.unloaded_asset_packs = true;
|
||||
asset_packs_free();
|
||||
@@ -513,10 +528,6 @@ static LoaderMessageLoaderStatusResult loader_start_external_app(
|
||||
result.value = loader_make_success_status(error_message);
|
||||
result.error = LoaderStatusErrorUnknown;
|
||||
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationBeforeLoad;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
|
||||
do {
|
||||
loader->app.fap = flipper_application_alloc(storage, firmware_api_interface);
|
||||
size_t start = furi_get_tick();
|
||||
@@ -628,6 +639,7 @@ static LoaderMessageLoaderStatusResult loader_start_external_app(
|
||||
if(result.value != LoaderStatusOk) {
|
||||
flipper_application_free(loader->app.fap);
|
||||
loader->app.fap = NULL;
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationLoadFailed;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
if(loader->app.unloaded_asset_packs) {
|
||||
@@ -681,6 +693,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
|
||||
status.value = loader_make_success_status(error_message);
|
||||
status.error = LoaderStatusErrorUnknown;
|
||||
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeApplicationBeforeLoad;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
|
||||
do {
|
||||
// check lock
|
||||
if(loader_do_is_locked(loader)) {
|
||||
@@ -745,6 +761,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
|
||||
LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name);
|
||||
} while(false);
|
||||
|
||||
if(status.value == LoaderStatusOk) {
|
||||
loader->app.launch_path = furi_string_alloc_set_str(name);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -762,6 +782,57 @@ static void loader_do_unlock(Loader* loader) {
|
||||
loader->app.thread = NULL;
|
||||
}
|
||||
|
||||
static void loader_do_emit_queue_empty_event(Loader* loader) {
|
||||
FURI_LOG_I(TAG, "Launch queue empty");
|
||||
LoaderEvent event;
|
||||
event.type = LoaderEventTypeNoMoreAppsInQueue;
|
||||
furi_pubsub_publish(loader->pubsub, &event);
|
||||
}
|
||||
|
||||
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record);
|
||||
|
||||
static void loader_do_next_deferred_launch_if_available(Loader* loader) {
|
||||
LoaderDeferredLaunchRecord record;
|
||||
if(loader_queue_pop(&loader->launch_queue, &record)) {
|
||||
loader_do_deferred_launch(loader, &record);
|
||||
loader_queue_item_clear(&record);
|
||||
} else {
|
||||
loader_do_emit_queue_empty_event(loader);
|
||||
}
|
||||
}
|
||||
|
||||
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record) {
|
||||
furi_assert(loader);
|
||||
furi_assert(record);
|
||||
|
||||
bool is_successful = false;
|
||||
FuriString* error_message = furi_string_alloc();
|
||||
view_holder_set_view(loader->view_holder, loading_get_view(loader->loading));
|
||||
view_holder_send_to_front(loader->view_holder);
|
||||
|
||||
do {
|
||||
const char* app_name_str = record->name_or_path;
|
||||
const char* app_args = record->args;
|
||||
FURI_LOG_I(TAG, "Deferred launch: %s", app_name_str);
|
||||
|
||||
LoaderMessageLoaderStatusResult result =
|
||||
loader_do_start_by_name(loader, app_name_str, app_args, error_message);
|
||||
if(result.value == LoaderStatusOk) {
|
||||
is_successful = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(record->flags & LoaderDeferredLaunchFlagGui)
|
||||
loader_show_gui_error(result, app_name_str, error_message);
|
||||
|
||||
loader_do_next_deferred_launch_if_available(loader);
|
||||
} while(false);
|
||||
|
||||
view_holder_set_view(loader->view_holder, NULL);
|
||||
furi_string_free(error_message);
|
||||
return is_successful;
|
||||
}
|
||||
|
||||
static void loader_do_app_closed(Loader* loader) {
|
||||
furi_assert(loader->app.thread);
|
||||
|
||||
@@ -786,6 +857,8 @@ static void loader_do_app_closed(Loader* loader) {
|
||||
loader->app.thread = NULL;
|
||||
}
|
||||
|
||||
furi_string_free(loader->app.launch_path);
|
||||
|
||||
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
|
||||
|
||||
LoaderEvent event;
|
||||
@@ -794,6 +867,8 @@ static void loader_do_app_closed(Loader* loader) {
|
||||
if(loader->app.unloaded_asset_packs) {
|
||||
asset_packs_init();
|
||||
}
|
||||
|
||||
loader_do_next_deferred_launch_if_available(loader);
|
||||
}
|
||||
|
||||
static bool loader_is_application_running(Loader* loader) {
|
||||
@@ -818,6 +893,15 @@ static bool loader_do_get_application_name(Loader* loader, FuriString* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool loader_do_get_application_launch_path(Loader* loader, FuriString* path) {
|
||||
if(loader_is_application_running(loader)) {
|
||||
furi_string_set(path, loader->app.launch_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// app
|
||||
|
||||
int32_t loader_srv(void* p) {
|
||||
@@ -840,16 +924,20 @@ int32_t loader_srv(void* p) {
|
||||
while(true) {
|
||||
if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
|
||||
switch(message.type) {
|
||||
case LoaderMessageTypeStartByName:
|
||||
*(message.status_value) = loader_do_start_by_name(
|
||||
case LoaderMessageTypeStartByName: {
|
||||
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
|
||||
loader, message.start.name, message.start.args, message.start.error_message);
|
||||
*(message.status_value) = status;
|
||||
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
}
|
||||
case LoaderMessageTypeStartByNameDetachedWithGuiError: {
|
||||
FuriString* error_message = furi_string_alloc();
|
||||
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
|
||||
loader, message.start.name, message.start.args, error_message);
|
||||
loader_show_gui_error(status, message.start.name, error_message);
|
||||
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
|
||||
if(message.start.name) free((void*)message.start.name);
|
||||
if(message.start.args) free((void*)message.start.args);
|
||||
furi_string_free(error_message);
|
||||
@@ -891,6 +979,19 @@ int32_t loader_srv(void* p) {
|
||||
loader_do_get_application_name(loader, message.application_name);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeGetApplicationLaunchPath:
|
||||
message.bool_value->value =
|
||||
loader_do_get_application_launch_path(loader, message.application_name);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeEnqueueLaunch:
|
||||
furi_check(loader_queue_push(&loader->launch_queue, &message.defer_start));
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
case LoaderMessageTypeClearLaunchQueue:
|
||||
loader_queue_clear(&loader->launch_queue);
|
||||
api_lock_unlock(message.api_lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,19 @@ typedef enum {
|
||||
typedef enum {
|
||||
LoaderEventTypeApplicationBeforeLoad,
|
||||
LoaderEventTypeApplicationLoadFailed,
|
||||
LoaderEventTypeApplicationStopped
|
||||
LoaderEventTypeApplicationStopped,
|
||||
LoaderEventTypeNoMoreAppsInQueue, //<! The normal `Stopped` event still fires before this one
|
||||
} LoaderEventType;
|
||||
|
||||
typedef struct {
|
||||
LoaderEventType type;
|
||||
} LoaderEvent;
|
||||
|
||||
typedef enum {
|
||||
LoaderDeferredLaunchFlagNone = 0,
|
||||
LoaderDeferredLaunchFlagGui = (1 << 1), //<! Report launch to the user via a dialog
|
||||
} LoaderDeferredLaunchFlag;
|
||||
|
||||
/**
|
||||
* @brief Start application
|
||||
* @param[in] instance loader instance
|
||||
@@ -99,7 +105,7 @@ FuriPubSub* loader_get_pubsub(Loader* instance);
|
||||
*
|
||||
* @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)
|
||||
* @param[inout] arg optional argument (can be of any value, including NULL)
|
||||
*
|
||||
* @return true if the signal was handled by the application, false otherwise
|
||||
*/
|
||||
@@ -109,11 +115,49 @@ 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)
|
||||
* @param[inout] name pointer to the string to contain the name (must be allocated)
|
||||
* @return true if it was possible to get an application name, false otherwise
|
||||
*/
|
||||
bool loader_get_application_name(Loader* instance, FuriString* name);
|
||||
|
||||
/**
|
||||
* @brief Get the launch path or name of the currently running application
|
||||
*
|
||||
* This is the string that was supplied to `loader_start` such that the current
|
||||
* app is running now. It might be a name (in the case of internal apps) or a
|
||||
* path (in the case of external apps). This value can be used to launch the
|
||||
* same app again.
|
||||
*
|
||||
* @param[in] instance pointer to the loader instance
|
||||
* @param[inout] name pointer to the string to contain the path or name
|
||||
* (must be allocated)
|
||||
* @return true if it was possible to get an application path, false otherwise
|
||||
*/
|
||||
bool loader_get_application_launch_path(Loader* instance, FuriString* name);
|
||||
|
||||
/**
|
||||
* @brief Enqueues a request to launch an application after the current one
|
||||
*
|
||||
* @param[in] instance pointer to the loader instance
|
||||
* @param[in] name pointer to the name or path of the application, or NULL to
|
||||
* cancel a previous request
|
||||
* @param[in] args pointer to argument to provide to the next app
|
||||
* @param[in] flags additional flags. see enum documentation for more info
|
||||
*/
|
||||
void loader_enqueue_launch(
|
||||
Loader* instance,
|
||||
const char* name,
|
||||
const char* args,
|
||||
LoaderDeferredLaunchFlag flags);
|
||||
|
||||
/**
|
||||
* @brief Removes all requests to launch applications after the current one
|
||||
* exits
|
||||
*
|
||||
* @param[in] instance pointer to the loader instance
|
||||
*/
|
||||
void loader_clear_launch_queue(Loader* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -124,7 +124,7 @@ static void loader_pubsub_callback(const void* message, void* context) {
|
||||
const LoaderEvent* event = message;
|
||||
const FuriThreadId thread_id = (FuriThreadId)context;
|
||||
|
||||
if(event->type == LoaderEventTypeApplicationStopped) {
|
||||
if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "loader.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <applications.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
@@ -143,4 +143,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_LOADER);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(loader, execute, CliCommandFlagParallelSafe, 1024);
|
||||
CLI_COMMAND_INTERFACE(loader, execute, CliCommandFlagParallelSafe, 1024, CLI_APPID);
|
||||
|
||||
@@ -2,11 +2,20 @@
|
||||
#include <furi.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/loading.h>
|
||||
|
||||
#include <m-array.h>
|
||||
|
||||
#include "loader.h"
|
||||
#include "loader_menu.h"
|
||||
#include "loader_applications.h"
|
||||
#include "loader_queue.h"
|
||||
|
||||
typedef struct {
|
||||
FuriString* launch_path;
|
||||
char* args;
|
||||
FuriThread* thread;
|
||||
bool insomniac;
|
||||
@@ -21,6 +30,12 @@ struct Loader {
|
||||
LoaderMenu* loader_menu;
|
||||
LoaderApplications* loader_applications;
|
||||
LoaderAppData app;
|
||||
|
||||
LoaderLaunchQueue launch_queue;
|
||||
|
||||
Gui* gui;
|
||||
ViewHolder* view_holder;
|
||||
Loading* loading;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -35,6 +50,9 @@ typedef enum {
|
||||
LoaderMessageTypeStartByNameDetachedWithGuiError,
|
||||
LoaderMessageTypeSignal,
|
||||
LoaderMessageTypeGetApplicationName,
|
||||
LoaderMessageTypeGetApplicationLaunchPath,
|
||||
LoaderMessageTypeEnqueueLaunch,
|
||||
LoaderMessageTypeClearLaunchQueue,
|
||||
|
||||
LoaderMessageTypeShowSettings,
|
||||
} LoaderMessageType;
|
||||
@@ -76,6 +94,7 @@ typedef struct {
|
||||
|
||||
union {
|
||||
LoaderMessageStartByName start;
|
||||
LoaderDeferredLaunchRecord defer_start;
|
||||
LoaderMessageSignal signal;
|
||||
FuriString* application_name;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <assets_icons.h>
|
||||
#include <applications.h>
|
||||
#include <archive/helpers/archive_favorites.h>
|
||||
#include <toolbox/run_parallel.h>
|
||||
|
||||
#include "loader.h"
|
||||
@@ -55,9 +56,7 @@ static void loader_pubsub_callback(const void* message, void* context) {
|
||||
furi_thread_free(loader_menu->thread);
|
||||
loader_menu->thread = NULL;
|
||||
}
|
||||
} else if(
|
||||
event->type == LoaderEventTypeApplicationLoadFailed ||
|
||||
event->type == LoaderEventTypeApplicationStopped) {
|
||||
} else if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
if(!loader_menu->thread) {
|
||||
loader_menu->thread = furi_thread_alloc_ex(TAG, 2048, loader_menu_thread, loader_menu);
|
||||
furi_thread_start(loader_menu->thread);
|
||||
@@ -170,25 +169,37 @@ static void loader_menu_applications_callback(void* context, uint32_t index) {
|
||||
loader_menu_start(name);
|
||||
}
|
||||
|
||||
static void loader_menu_settings_menu_callback(void* context, uint32_t index) {
|
||||
// Can't do this in GUI callbacks because now ViewHolder waits for ongoing
|
||||
// input, and inputs are not processed because GUI is processing callbacks
|
||||
static int32_t loader_menu_setting_pin_unpin_parallel(void* context) {
|
||||
const char* name = context;
|
||||
archive_favorites_handle_setting_pin_unpin(name, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
loader_menu_settings_menu_callback(void* context, InputType input_type, uint32_t index) {
|
||||
UNUSED(context);
|
||||
const char* name = FLIPPER_SETTINGS_APPS[index].name;
|
||||
|
||||
// Workaround for SD format when app can't be opened
|
||||
if(!strcmp(name, "Storage")) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FS_Error status = storage_sd_status(storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
// If SD card not ready, cannot be formatted, so we want loader to give
|
||||
// normal error message, with function below
|
||||
if(status != FSE_NOT_READY) {
|
||||
// Attempt to launch the app, and if failed offer to format SD card
|
||||
run_parallel(loader_menu_storage_settings, storage, 512);
|
||||
return;
|
||||
if(input_type == InputTypeShort) {
|
||||
// Workaround for SD format when app can't be opened
|
||||
if(!strcmp(name, "Storage")) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FS_Error status = storage_sd_status(storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
// If SD card not ready, cannot be formatted, so we want loader to give
|
||||
// normal error message, with function below
|
||||
if(status != FSE_NOT_READY) {
|
||||
// Attempt to launch the app, and if failed offer to format SD card
|
||||
run_parallel(loader_menu_storage_settings, storage, 512);
|
||||
return;
|
||||
}
|
||||
}
|
||||
loader_menu_start(name);
|
||||
} else if(input_type == InputTypeLong) {
|
||||
run_parallel(loader_menu_setting_pin_unpin_parallel, (void*)name, 512);
|
||||
}
|
||||
|
||||
loader_menu_start(name);
|
||||
}
|
||||
|
||||
// Can't do this in GUI callbacks because now ViewHolder waits for ongoing
|
||||
@@ -357,7 +368,7 @@ static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) {
|
||||
|
||||
static void loader_menu_build_submenu(LoaderMenuApp* app, LoaderMenu* loader_menu) {
|
||||
for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) {
|
||||
submenu_add_item(
|
||||
submenu_add_item_ex(
|
||||
app->settings_menu,
|
||||
FLIPPER_SETTINGS_APPS[i].name,
|
||||
i,
|
||||
|
||||
32
applications/services/loader/loader_queue.c
Normal file
32
applications/services/loader/loader_queue.c
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "loader_queue.h"
|
||||
|
||||
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item) {
|
||||
free(item->args);
|
||||
free(item->name_or_path);
|
||||
}
|
||||
|
||||
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
|
||||
if(!queue->item_cnt) return false;
|
||||
|
||||
*item = queue->items[0];
|
||||
queue->item_cnt--;
|
||||
memmove(
|
||||
&queue->items[0], &queue->items[1], queue->item_cnt * sizeof(LoaderDeferredLaunchRecord));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
|
||||
if(queue->item_cnt == LOADER_QUEUE_MAX_SIZE) return false;
|
||||
|
||||
queue->items[queue->item_cnt] = *item;
|
||||
queue->item_cnt++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void loader_queue_clear(LoaderLaunchQueue* queue) {
|
||||
for(size_t i = 0; i < queue->item_cnt; i++)
|
||||
loader_queue_item_clear(&queue->items[i]);
|
||||
queue->item_cnt = 0;
|
||||
}
|
||||
53
applications/services/loader/loader_queue.h
Normal file
53
applications/services/loader/loader_queue.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "loader.h"
|
||||
|
||||
#define LOADER_QUEUE_MAX_SIZE 4
|
||||
|
||||
typedef struct {
|
||||
char* name_or_path;
|
||||
char* args;
|
||||
LoaderDeferredLaunchFlag flags;
|
||||
} LoaderDeferredLaunchRecord;
|
||||
|
||||
typedef struct {
|
||||
LoaderDeferredLaunchRecord items[LOADER_QUEUE_MAX_SIZE];
|
||||
size_t item_cnt;
|
||||
} LoaderLaunchQueue;
|
||||
|
||||
/**
|
||||
* @brief Frees internal data in a `DeferredLaunchRecord`
|
||||
*
|
||||
* @param[out] item Record to clear
|
||||
*/
|
||||
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item);
|
||||
|
||||
/**
|
||||
* @brief Fetches the next item from the launch queue
|
||||
*
|
||||
* @param[inout] queue Queue instance
|
||||
* @param[out] item Item output
|
||||
*
|
||||
* @return `true` if `item` was populated, `false` if queue is empty
|
||||
*/
|
||||
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
|
||||
|
||||
/**
|
||||
* @brief Puts an item into the launch queue
|
||||
*
|
||||
* @param[inout] queue Queue instance
|
||||
* @param[in] item Item to put in the queue
|
||||
*
|
||||
* @return `true` if the item was put into the queue, `false` if there's no more
|
||||
* space left
|
||||
*/
|
||||
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
|
||||
|
||||
/**
|
||||
* @brief Clears the launch queue
|
||||
*
|
||||
* @param[inout] queue Queue instance
|
||||
*/
|
||||
void loader_queue_clear(LoaderLaunchQueue* queue);
|
||||
@@ -4,6 +4,6 @@ App(
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="locale_on_system_start",
|
||||
cdefines=["SRV_LOCALE"],
|
||||
order=90,
|
||||
order=70,
|
||||
sdk_headers=["locale.h"],
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "power_cli.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <toolbox/pipe.h>
|
||||
@@ -113,4 +113,4 @@ static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(power, execute, CliCommandFlagParallelSafe, 1024);
|
||||
CLI_COMMAND_INTERFACE(power, execute, CliCommandFlagParallelSafe, 1024, CLI_APPID);
|
||||
|
||||
@@ -462,9 +462,7 @@ static void power_loader_callback(const void* message, void* context) {
|
||||
power->app_running = true;
|
||||
power_auto_poweroff_disarm(power);
|
||||
// arm timer if some apps was not loaded or was stoped
|
||||
} else if(
|
||||
event->type == LoaderEventTypeApplicationLoadFailed ||
|
||||
event->type == LoaderEventTypeApplicationStopped) {
|
||||
} else if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
power->app_running = false;
|
||||
power_auto_poweroff_arm(power);
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ App(
|
||||
entry_point="region_on_system_start",
|
||||
cdefines=["SRV_REGION"],
|
||||
requires=["storage"],
|
||||
order=170,
|
||||
order=120,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <m-dict.h>
|
||||
@@ -447,9 +448,14 @@ void rpc_on_system_start(void* p) {
|
||||
|
||||
rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(
|
||||
cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc);
|
||||
CliRegistry* registry = furi_record_open(RECORD_CLI);
|
||||
cli_registry_add_command(
|
||||
registry,
|
||||
"start_rpc_session",
|
||||
CliCommandFlagParallelSafe,
|
||||
rpc_cli_command_start_session,
|
||||
rpc);
|
||||
furi_record_close(RECORD_CLI);
|
||||
|
||||
furi_record_create(RECORD_RPC, rpc);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <furi.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user