Merge remote-tracking branch 'ofw/dev' into mntm-dev

This commit is contained in:
Willy-JL
2025-04-12 06:35:36 +01:00
151 changed files with 2802 additions and 1745 deletions

4
.gitignore vendored
View File

@@ -68,3 +68,7 @@ PVS-Studio.log
# JS packages
node_modules/
# cli_perf script output in case of errors
/block.bin
/return_block.bin

View File

@@ -6,6 +6,5 @@ App(
entry_point="accessor_app",
requires=["gui"],
stack_size=4 * 1024,
order=40,
fap_category="Debug",
)

View File

@@ -8,6 +8,5 @@ App(
"power",
],
stack_size=1 * 1024,
order=130,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="blink_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=10,
fap_category="Debug",
)

View File

@@ -13,6 +13,5 @@ App(
"bt_debug",
],
stack_size=1 * 1024,
order=110,
fap_category="Debug",
)

View File

@@ -10,6 +10,5 @@ App(
"ccid_test",
],
stack_size=1 * 1024,
order=120,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="direct_draw_app",
requires=["gui", "input"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)

View File

@@ -6,6 +6,5 @@ App(
requires=["gui"],
fap_libs=["u8g2"],
stack_size=1 * 1024,
order=120,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="event_loop_blink_test_app",
requires=["input"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)

View File

@@ -6,7 +6,6 @@ App(
requires=["expansion_start"],
fap_libs=["assets"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
fap_file_assets="assets",
)

View File

@@ -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",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="keypad_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=30,
fap_category="Debug",
)

View File

@@ -11,6 +11,5 @@ App(
"lfrfid_debug",
],
stack_size=1 * 1024,
order=100,
fap_category="Debug",
)

View 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",
)

View 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;
}

View 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",
)

View 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;
}

View File

@@ -5,6 +5,5 @@ App(
entry_point="locale_test_app",
requires=["gui", "locale"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="rpc_debug_app",
requires=["gui", "rpc_start", "notification"],
stack_size=2 * 1024,
order=10,
fap_category="Debug",
)

View File

@@ -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"],
)

View File

@@ -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) {

View File

@@ -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",

View File

@@ -5,6 +5,5 @@ App(
entry_point="text_box_element_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="text_box_view_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="uart_echo_app",
requires=["gui"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)

View File

@@ -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(

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()) {

View File

@@ -5,6 +5,5 @@ App(
entry_point="usb_mouse_app",
requires=["gui"],
stack_size=1 * 1024,
order=60,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="usb_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=50,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="vibro_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)

View File

@@ -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",
],

View File

@@ -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);

View File

@@ -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,
};

View File

@@ -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) {

View File

@@ -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[] = {

View File

@@ -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);
}

View File

@@ -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)));

View File

@@ -23,12 +23,13 @@ typedef enum {
ArchiveFileTypeMag,
ArchiveFileTypeCrossRemote,
ArchiveFileTypeU2f,
ArchiveFileTypeUpdateManifest,
ArchiveFileTypeApplication,
ArchiveFileTypeJS,
ArchiveFileTypeSearch,
ArchiveFileTypeUpdateManifest,
ArchiveFileTypeDiskImage,
ArchiveFileTypeFolder,
ArchiveFileTypeSetting,
ArchiveFileTypeUnknown,
ArchiveFileTypeAppOrJs,
ArchiveFileTypeLoading,

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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,

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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};

View File

@@ -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;
}
}

View File

@@ -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(

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -1,5 +1,3 @@
#pragma once
#include <cli/cli.h>
void subghz_on_system_start(void);

View File

@@ -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);

View File

@@ -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"],
)

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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");

View File

@@ -1,6 +1,5 @@
#pragma once
#include "cli_i.h"
#include <toolbox/pipe.h>
void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context);

View File

@@ -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; \
}

View File

@@ -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

View File

@@ -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);
}

View 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);

View 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,
};

View 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;

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View 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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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));

View File

@@ -8,5 +8,5 @@ App(
],
requires=["rpc_start"],
provides=["expansion_settings"],
order=150,
order=100,
)

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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,

View 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;
}

View 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);

View File

@@ -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"],
)

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -6,5 +6,5 @@ App(
entry_point="region_on_system_start",
cdefines=["SRV_REGION"],
requires=["storage"],
order=170,
order=120,
)

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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