1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 12:51:22 +04:00

[FL-3965] Separate cli_shell into toolbox (#4175)

* cli_shell: separate into toolbox

* fix: cmd flags

* fix formatting

* cli: increase default stack depth

* cli_shell: fix loader lock logic

* cli: fix command flags

* fix f18

* speaker_debug: fix

* cli_registry: fix docs

* ufbt: rename cli target back

* cli: rename app and record

* cli: fix and simplify help command

* cli_master_shell: fix ext commands

* fix formatting

* cli: rename master to main

* fix formatting

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
This commit is contained in:
Anna Antonenko
2025-04-05 02:58:58 +04:00
committed by GitHub
parent 6f852e646c
commit 7192c9e68b
60 changed files with 994 additions and 683 deletions

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

@@ -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,6 +1,8 @@
#include <furi.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 "test_runner.h"
@@ -14,8 +16,9 @@ void unit_tests_cli(PipeSide* pipe, FuriString* args, 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
}

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

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

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,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>
@@ -1184,4 +1185,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,8 +1,9 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli.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_settings.h"
@@ -230,8 +231,8 @@ static void bt_cli(PipeSide* pipe, FuriString* args, void* context) {
void bt_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "bt", CliCommandFlagDefault, bt_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(bt_cli);

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
@@ -49,3 +41,11 @@ App(
requires=["cli"],
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"],
)

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 +0,0 @@
/**
* @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
#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 = { \
.appid = PLUGIN_APP_ID, \
.ep_api_version = PLUGIN_API_VERSION, \
.entry_point = &cli_##name##_desc, \
}; \
\
const FlipperAppPluginDescriptor* cli_##name##_ep(void) { \
return &plugin_descriptor; \
}

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);
@@ -514,16 +470,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)
*/
@@ -545,27 +491,32 @@ void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) {
}
}
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_registry_add_command(
registry, "uptime", CliCommandFlagParallelSafe, cli_command_uptime, NULL);
cli_registry_add_command(registry, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
cli_registry_add_command(registry, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_registry_add_command(registry, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
cli_registry_add_command(registry, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
cli_registry_add_command(
registry, "free_blocks", CliCommandFlagDefault, cli_command_free_blocks, NULL);
cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL);
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, 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_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL);
cli_registry_add_command(registry, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
cli_registry_add_command(registry, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
}
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 <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_registry.h>
#define RECORD_CLI "cli"
#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"
@@ -47,10 +49,12 @@ struct CliVcp {
FuriHalUsbInterface* previous_interface;
PipeSide* own_pipe;
PipeSide* shell_pipe;
bool is_currently_transmitting;
size_t previous_tx_length;
FuriThread* shell;
CliRegistry* main_registry;
CliShell* shell;
};
// ============
@@ -210,13 +214,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,7 +230,9 @@ 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);
}
if(event & CliVcpInternalEventRx) {
@@ -260,6 +268,8 @@ static CliVcp* cli_vcp_alloc(void) {
furi_event_loop_subscribe_thread_flags(
cli_vcp->event_loop, 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,7 +3,9 @@
#include <lib/toolbox/args.h>
#include <toolbox/pipe.h>
#include <cli/cli.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");
@@ -319,8 +321,8 @@ static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) {
void crypto_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(crypto_cli);

View File

@@ -1,6 +1,5 @@
#include "desktop_i.h"
#include <cli/cli.h>
#include <cli/cli_vcp.h>
#include <gui/gui_i.h>

View File

@@ -4,8 +4,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>
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
@@ -93,8 +94,10 @@ int32_t input_srv(void* p) {
#endif
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
furi_record_close(RECORD_CLI);
#endif
InputPinState pin_states[input_pins_count];

View File

@@ -1,7 +1,7 @@
#include "input.h"
#include <furi.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <toolbox/args.h>
#include <toolbox/pipe.h>

View File

@@ -1,7 +1,8 @@
#include "loader.h"
#include <furi.h>
#include <cli/cli.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>
@@ -141,8 +142,9 @@ static void loader_cli(PipeSide* pipe, FuriString* args, void* context) {
void loader_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(loader_cli);

View File

@@ -1,7 +1,8 @@
#include "power_cli.h"
#include <furi_hal.h>
#include <cli/cli.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>
@@ -114,10 +115,8 @@ void power_cli(PipeSide* pipe, FuriString* args, void* context) {
void power_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "power", CliCommandFlagParallelSafe, power_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(power_cli);

View File

@@ -9,7 +9,8 @@
#include <furi.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>
@@ -435,9 +436,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

View File

@@ -1,7 +1,9 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_ansi.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h>
@@ -695,16 +697,15 @@ static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* co
void storage_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command_ex(
cli,
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry,
"storage",
CliCommandFlagParallelSafe | CliCommandFlagUseShellThread,
storage_cli,
NULL,
512);
cli_add_command(
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
NULL);
cli_registry_add_command(
registry, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(storage_cli_factory_reset);

View File

@@ -4,7 +4,8 @@
#include "js_app_i.h"
#include <toolbox/path.h>
#include <assets_icons.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <toolbox/pipe.h>
#define TAG "JS app"
@@ -209,8 +210,8 @@ void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) {
void js_app_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "js", CliCommandFlagDefault, js_cli_execute, NULL);
furi_record_close(RECORD_CLI);
#endif
}

View File

@@ -1,7 +1,8 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <storage/storage.h>
#include <loader/loader.h>
#include <toolbox/path.h>
@@ -106,8 +107,8 @@ static void updater_start_app(void* context, uint32_t arg) {
void updater_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
cli_add_command(cli, "update", CliCommandFlagDefault, updater_cli_ep, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "update", CliCommandFlagDefault, updater_cli_ep, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(updater_cli_ep);

View File

@@ -12,6 +12,10 @@ env.Append(
],
SDK_HEADERS=[
File("api_lock.h"),
File("cli/cli_ansi.h"),
File("cli/cli_command.h"),
File("cli/cli_registry.h"),
File("cli/shell/cli_shell.h"),
File("compress.h"),
File("manchester_decoder.h"),
File("manchester_encoder.h"),

View File

@@ -1,6 +1,6 @@
#pragma once
#include "cli.h"
#include <furi.h>
#ifdef __cplusplus
extern "C" {

View File

@@ -0,0 +1,17 @@
#include "cli_command.h"
#include "cli_ansi.h"
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);
}

View File

@@ -0,0 +1,103 @@
/**
* @file cli_command.h
* Command metadata and helpers
*/
#pragma once
#include <furi.h>
#include <toolbox/pipe.h>
#include <lib/flipper_application/flipper_application.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_PLUGIN_API_VERSION 1
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;
/**
* @brief CLI command execution callback pointer
*
* 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 (*CliCommandExecuteCallback)(PipeSide* pipe, FuriString* args, void* context);
typedef struct {
char* name;
CliCommandExecuteCallback execute_callback;
CliCommandFlag flags;
size_t stack_depth;
} CliCommandDescriptor;
/**
* @brief Configuration for locating external commands
*/
typedef struct {
const char* search_directory; //<! The directory to look in
const char* fal_prefix; //<! File name prefix that commands should have
const char* appid; //<! Expected plugin-reported appid
} CliCommandExternalConfig;
/**
* @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 0 or 1 bytes 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);
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth, app_id) \
static const CliCommandDescriptor cli_##name##_desc = { \
#name, \
&execute_callback, \
flags, \
stack_depth, \
}; \
\
static const FlipperAppPluginDescriptor plugin_descriptor = { \
.appid = app_id, \
.ep_api_version = CLI_PLUGIN_API_VERSION, \
.entry_point = &cli_##name##_desc, \
}; \
\
const FlipperAppPluginDescriptor* cli_##name##_ep(void) { \
return &plugin_descriptor; \
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,178 @@
#include "cli_registry.h"
#include "cli_registry_i.h"
#include <toolbox/pipe.h>
#include <storage/storage.h>
#define TAG "cli"
struct CliRegistry {
CliCommandTree_t commands;
FuriMutex* mutex;
};
CliRegistry* cli_registry_alloc(void) {
CliRegistry* registry = malloc(sizeof(CliRegistry));
CliCommandTree_init(registry->commands);
registry->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
return registry;
}
void cli_registry_free(CliRegistry* registry) {
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
furi_mutex_free(registry->mutex);
CliCommandTree_clear(registry->commands);
free(registry);
}
void cli_registry_add_command(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context) {
cli_registry_add_command_ex(
registry, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE);
}
void cli_registry_add_command_ex(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context,
size_t stack_size) {
furi_check(registry);
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);
CliRegistryCommand command = {
.context = context,
.execute_callback = callback,
.flags = flags,
.stack_depth = stack_size,
};
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandTree_set_at(registry->commands, name_str, command);
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
furi_string_free(name_str);
}
void cli_registry_delete_command(CliRegistry* registry, const char* name) {
furi_check(registry);
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(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandTree_erase(registry->commands, name_str);
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
furi_string_free(name_str);
}
bool cli_registry_get_command(
CliRegistry* registry,
FuriString* command,
CliRegistryCommand* result) {
furi_assert(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliRegistryCommand* data = CliCommandTree_get(registry->commands, command);
if(data) *result = *data;
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
return !!data;
}
void cli_registry_remove_external_commands(CliRegistry* registry) {
furi_check(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
// FIXME FL-3977: memory leak somewhere within this function
CliCommandTree_t internal_cmds;
CliCommandTree_init(internal_cmds);
for
M_EACH(item, registry->commands, CliCommandTree_t) {
if(!(item->value_ptr->flags & CliCommandFlagExternal))
CliCommandTree_set_at(internal_cmds, *item->key_ptr, *item->value_ptr);
}
CliCommandTree_move(registry->commands, internal_cmds);
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
}
void cli_registry_reload_external_commands(
CliRegistry* registry,
const CliCommandExternalConfig* config) {
furi_check(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
FURI_LOG_D(TAG, "Reloading ext commands");
cli_registry_remove_external_commands(registry);
// 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, config->search_directory)) {
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_check(furi_string_end_with_str(plugin_name, ".fal"));
furi_string_replace_all_str(plugin_name, ".fal", "");
furi_check(furi_string_start_with_str(plugin_name, config->fal_prefix));
furi_string_replace_at(plugin_name, 0, strlen(config->fal_prefix), "");
CliRegistryCommand command = {
.context = NULL,
.execute_callback = NULL,
.flags = CliCommandFlagExternal,
};
CliCommandTree_set_at(registry->commands, plugin_name, command);
}
furi_string_free(plugin_name);
}
storage_dir_close(plugin_dir);
storage_file_free(plugin_dir);
furi_record_close(RECORD_STORAGE);
FURI_LOG_D(TAG, "Done reloading ext commands");
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
}
void cli_registry_lock(CliRegistry* registry) {
furi_assert(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
}
void cli_registry_unlock(CliRegistry* registry) {
furi_assert(registry);
furi_mutex_release(registry->mutex);
}
CliCommandTree_t* cli_registry_get_commands(CliRegistry* registry) {
furi_assert(registry);
return &registry->commands;
}

View File

@@ -0,0 +1,92 @@
/**
* @file cli_registry.h
* API for registering commands with a CLI shell
*/
#pragma once
#include <furi.h>
#include <m-array.h>
#include <toolbox/pipe.h>
#include "cli_command.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliRegistry CliRegistry;
/**
* @brief Allocates a `CliRegistry`.
*/
CliRegistry* cli_registry_alloc(void);
/**
* @brief Frees a `CliRegistry`.
*/
void cli_registry_free(CliRegistry* registry);
/**
* @brief Registers a command with the registry. Provides less options than the
* `_ex` counterpart.
*
* @param [in] registry Pointer to registry instance
* @param [in] name Command name
* @param [in] flags see CliCommandFlag
* @param [in] callback Callback function
* @param [in] context Custom context
*/
void cli_registry_add_command(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context);
/**
* @brief Registers a command with the registry. Provides more options than the
* non-`_ex` counterpart.
*
* @param [in] registry Pointer to registry instance
* @param [in] name Command name
* @param [in] flags see CliCommandFlag
* @param [in] callback Callback function
* @param [in] context Custom context
* @param [in] stack_size Thread stack size
*/
void cli_registry_add_command_ex(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context,
size_t stack_size);
/**
* @brief Deletes a cli command
*
* @param [in] registry Pointer to registry instance
* @param [in] name Command name
*/
void cli_registry_delete_command(CliRegistry* registry, const char* name);
/**
* @brief Unregisters all external commands
*
* @param [in] registry Pointer to registry instance
*/
void cli_registry_remove_external_commands(CliRegistry* registry);
/**
* @brief Reloads the list of externally available commands
*
* @param [in] registry Pointer to registry instance
* @param [in] config See `CliCommandExternalConfig`
*/
void cli_registry_reload_external_commands(
CliRegistry* registry,
const CliCommandExternalConfig* config);
#ifdef __cplusplus
}
#endif

View File

@@ -1,5 +1,5 @@
/**
* @file cli_i.h
* @file cli_registry_i.h
* Internal API for getting commands registered with the CLI
*/
@@ -7,21 +7,20 @@
#include <furi.h>
#include <m-bptree.h>
#include "cli.h"
#include "cli_registry.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U)
#define CLI_COMMANDS_PATH "/ext/apps_data/cli/plugins"
#define CLI_BUILTIN_COMMAND_STACK_SIZE (4 * 1024U)
typedef struct {
void* context; //<! Context passed to callbacks
CliExecuteCallback execute_callback; //<! Callback for command execution
CliCommandExecuteCallback execute_callback; //<! Callback for command execution
CliCommandFlag flags;
size_t stack_depth;
} CliCommand;
} CliRegistryCommand;
#define CLI_COMMANDS_TREE_RANK 4
@@ -32,21 +31,24 @@ BPTREE_DEF2(
CLI_COMMANDS_TREE_RANK,
FuriString*,
FURI_STRING_OPLIST,
CliCommand,
CliRegistryCommand,
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);
bool cli_registry_get_command(
CliRegistry* registry,
FuriString* command,
CliRegistryCommand* result);
void cli_lock_commands(Cli* cli);
void cli_registry_lock(CliRegistry* registry);
void cli_unlock_commands(Cli* cli);
void cli_registry_unlock(CliRegistry* registry);
/**
* @warning Surround calls to this function with `cli_[un]lock_commands`
* @warning Surround calls to this function with `cli_registry_[un]lock`
*/
CliCommandTree_t* cli_get_commands(Cli* cli);
CliCommandTree_t* cli_registry_get_commands(CliRegistry* registry);
#ifdef __cplusplus
}

View File

@@ -1,8 +1,8 @@
#include "cli_shell.h"
#include "cli_shell_i.h"
#include "../cli_ansi.h"
#include "../cli_i.h"
#include "../cli_commands.h"
#include "../cli_registry_i.h"
#include "../cli_command.h"
#include "cli_shell_line.h"
#include "cli_shell_completions.h"
#include <stdio.h>
@@ -36,10 +36,17 @@ typedef enum {
} CliShellStorageEvent;
struct CliShell {
Cli* cli;
FuriEventLoop* event_loop;
// Set and freed by external thread
CliShellMotd motd;
void* callback_context;
PipeSide* pipe;
CliRegistry* registry;
const CliCommandExternalConfig* ext_config;
FuriThread* thread;
const char* prompt;
// Set and freed by shell thread
FuriEventLoop* event_loop;
CliAnsiParser* ansi_parser;
FuriEventLoopTimer* ansi_parsing_timer;
@@ -51,7 +58,7 @@ struct CliShell {
};
typedef struct {
CliCommand* command;
CliRegistryCommand* command;
PipeSide* pipe;
FuriString* args;
} CliCommandThreadData;
@@ -73,9 +80,62 @@ static void cli_shell_detach_pipe(CliShell* cli_shell) {
furi_thread_set_stdout_callback(NULL, NULL);
}
// =========
// Execution
// =========
// =================
// Built-in commands
// =================
void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
CliShell* shell = context;
furi_check(shell->ext_config);
cli_registry_reload_external_commands(shell->registry, shell->ext_config);
printf("OK!");
}
void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
CliShell* shell = context;
CliRegistry* registry = shell->registry;
const size_t columns = 3;
printf("Available commands:\r\n" ANSI_FG_GREEN);
cli_registry_lock(registry);
CliCommandTree_t* commands = cli_registry_get_commands(registry);
size_t commands_count = CliCommandTree_size(*commands);
CliCommandTree_it_t iterator;
CliCommandTree_it(iterator, *commands);
for(size_t i = 0; i < commands_count; i++) {
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterator);
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
CliCommandTree_next(iterator);
if(i % columns == columns - 1) printf("\r\n");
}
if(shell->ext_config)
printf(
ANSI_RESET
"\r\nIf you added a new external 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_registry_unlock(registry);
}
void cli_command_exit(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
CliShell* shell = context;
cli_shell_line_set_about_to_exit(shell->components[CliShellComponentLine]);
furi_event_loop_stop(shell->event_loop);
}
// ==================
// Internal functions
// ==================
static int32_t cli_command_thread(void* context) {
CliCommandThreadData* thread_data = context;
@@ -99,12 +159,13 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
furi_string_right(args, space + 1);
PluginManager* plugin_manager = NULL;
Loader* loader = NULL;
CliCommand command_data;
Loader* loader = furi_record_open(RECORD_LOADER);
bool loader_locked = false;
CliRegistryCommand command_data;
do {
// find handler
if(!cli_get_command(cli_shell->cli, command_name, &command_data)) {
if(!cli_registry_get_command(cli_shell->registry, command_name, &command_data)) {
printf(
ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET,
furi_string_get_cstr(command_name));
@@ -113,10 +174,14 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
// load external command
if(command_data.flags & CliCommandFlagExternal) {
plugin_manager =
plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface);
const CliCommandExternalConfig* ext_config = cli_shell->ext_config;
plugin_manager = plugin_manager_alloc(
ext_config->appid, CLI_PLUGIN_API_VERSION, firmware_api_interface);
FuriString* path = furi_string_alloc_printf(
"%s/cli_%s.fal", CLI_COMMANDS_PATH, furi_string_get_cstr(command_name));
"%s/%s%s.fal",
ext_config->search_directory,
ext_config->fal_prefix,
furi_string_get_cstr(command_name));
uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager);
PluginManagerError error =
plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path));
@@ -141,9 +206,8 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
// lock loader
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
loader = furi_record_open(RECORD_LOADER);
bool success = loader_lock(loader);
if(!success) {
loader_locked = loader_lock(loader);
if(!loader_locked) {
printf(ANSI_FG_RED
"this command cannot be run while an application is open" ANSI_RESET);
break;
@@ -177,13 +241,17 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
furi_string_free(args);
// unlock loader
if(loader) loader_unlock(loader);
if(loader_locked) loader_unlock(loader);
furi_record_close(RECORD_LOADER);
// unload external command
if(plugin_manager) plugin_manager_free(plugin_manager);
}
const char* cli_shell_get_prompt(CliShell* cli_shell) {
return cli_shell->prompt;
}
// ==============
// Event handlers
// ==============
@@ -210,9 +278,9 @@ static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void*
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
if(event == CliShellStorageEventMount) {
cli_enumerate_external_commands(cli_shell->cli);
cli_registry_reload_external_commands(cli_shell->registry, cli_shell->ext_config);
} else if(event == CliShellStorageEventUnmount) {
cli_remove_external_commands(cli_shell->cli);
cli_registry_remove_external_commands(cli_shell->registry);
} else {
furi_crash();
}
@@ -265,113 +333,97 @@ static void cli_shell_timer_expired(void* context) {
cli_shell, cli_ansi_parser_feed_timeout(cli_shell->ansi_parser));
}
// =======
// Helpers
// =======
// ===========
// Thread code
// ===========
static CliShell* cli_shell_alloc(PipeSide* pipe) {
CliShell* cli_shell = malloc(sizeof(CliShell));
static void cli_shell_init(CliShell* shell) {
cli_registry_add_command(
shell->registry,
"help",
CliCommandFlagUseShellThread | CliCommandFlagParallelSafe,
cli_command_help,
shell);
cli_registry_add_command(
shell->registry,
"?",
CliCommandFlagUseShellThread | CliCommandFlagParallelSafe,
cli_command_help,
shell);
cli_registry_add_command(
shell->registry,
"exit",
CliCommandFlagUseShellThread | CliCommandFlagParallelSafe,
cli_command_exit,
shell);
cli_shell->cli = furi_record_open(RECORD_CLI);
cli_shell->ansi_parser = cli_ansi_parser_alloc();
cli_shell->pipe = pipe;
if(shell->ext_config) {
cli_registry_add_command(
shell->registry,
"reload_ext_cmds",
CliCommandFlagUseShellThread,
cli_command_reload_external,
shell);
cli_registry_reload_external_commands(shell->registry, shell->ext_config);
}
cli_shell->components[CliShellComponentLine] = cli_shell_line_alloc(cli_shell);
cli_shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc(
cli_shell->cli, cli_shell, cli_shell->components[CliShellComponentLine]);
shell->components[CliShellComponentLine] = cli_shell_line_alloc(shell);
shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc(
shell->registry, shell, shell->components[CliShellComponentLine]);
cli_shell->event_loop = furi_event_loop_alloc();
cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell);
shell->ansi_parser = cli_ansi_parser_alloc();
cli_shell_install_pipe(cli_shell);
shell->event_loop = furi_event_loop_alloc();
shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, shell);
cli_shell->storage_event_queue = furi_message_queue_alloc(1, sizeof(CliShellStorageEvent));
shell->storage_event_queue = furi_message_queue_alloc(1, sizeof(CliShellStorageEvent));
furi_event_loop_subscribe_message_queue(
cli_shell->event_loop,
cli_shell->storage_event_queue,
shell->event_loop,
shell->storage_event_queue,
FuriEventLoopEventIn,
cli_shell_storage_internal_event,
cli_shell);
cli_shell->storage = furi_record_open(RECORD_STORAGE);
cli_shell->storage_subscription = furi_pubsub_subscribe(
storage_get_pubsub(cli_shell->storage), cli_shell_storage_event, cli_shell);
shell);
shell->storage = furi_record_open(RECORD_STORAGE);
shell->storage_subscription =
furi_pubsub_subscribe(storage_get_pubsub(shell->storage), cli_shell_storage_event, shell);
return cli_shell;
cli_shell_install_pipe(shell);
}
static void cli_shell_free(CliShell* cli_shell) {
furi_pubsub_unsubscribe(
storage_get_pubsub(cli_shell->storage), cli_shell->storage_subscription);
static void cli_shell_deinit(CliShell* shell) {
furi_pubsub_unsubscribe(storage_get_pubsub(shell->storage), shell->storage_subscription);
furi_record_close(RECORD_STORAGE);
furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->storage_event_queue);
furi_message_queue_free(cli_shell->storage_event_queue);
furi_event_loop_unsubscribe(shell->event_loop, shell->storage_event_queue);
furi_message_queue_free(shell->storage_event_queue);
cli_shell_completions_free(cli_shell->components[CliShellComponentCompletions]);
cli_shell_line_free(cli_shell->components[CliShellComponentLine]);
cli_shell_completions_free(shell->components[CliShellComponentCompletions]);
cli_shell_line_free(shell->components[CliShellComponentLine]);
cli_shell_detach_pipe(cli_shell);
furi_event_loop_timer_free(cli_shell->ansi_parsing_timer);
furi_event_loop_free(cli_shell->event_loop);
pipe_free(cli_shell->pipe);
cli_ansi_parser_free(cli_shell->ansi_parser);
furi_record_close(RECORD_CLI);
free(cli_shell);
}
static void cli_shell_motd(void) {
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));
}
cli_shell_detach_pipe(shell);
furi_event_loop_timer_free(shell->ansi_parsing_timer);
furi_event_loop_free(shell->event_loop);
cli_ansi_parser_free(shell->ansi_parser);
}
static int32_t cli_shell_thread(void* context) {
PipeSide* pipe = context;
CliShell* shell = context;
// Sometimes, the other side closes the pipe even before our thread is started. Although the
// rest of the code will eventually find this out if this check is removed, there's no point in
// wasting time.
if(pipe_state(pipe) == PipeStateBroken) return 0;
CliShell* cli_shell = cli_shell_alloc(pipe);
if(pipe_state(shell->pipe) == PipeStateBroken) return 0;
cli_shell_init(shell);
FURI_LOG_D(TAG, "Started");
cli_shell_motd();
cli_shell_line_prompt(cli_shell->components[CliShellComponentLine]);
furi_event_loop_run(cli_shell->event_loop);
shell->motd(shell->callback_context);
cli_shell_line_prompt(shell->components[CliShellComponentLine]);
furi_event_loop_run(shell->event_loop);
FURI_LOG_D(TAG, "Stopped");
cli_shell_free(cli_shell);
cli_shell_deinit(shell);
return 0;
}
@@ -379,9 +431,48 @@ static int32_t cli_shell_thread(void* context) {
// Public API
// ==========
FuriThread* cli_shell_start(PipeSide* pipe) {
FuriThread* thread =
furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, pipe);
furi_thread_start(thread);
return thread;
CliShell* cli_shell_alloc(
CliShellMotd motd,
void* context,
PipeSide* pipe,
CliRegistry* registry,
const CliCommandExternalConfig* ext_config) {
furi_check(motd);
furi_check(pipe);
furi_check(registry);
CliShell* shell = malloc(sizeof(CliShell));
*shell = (CliShell){
.motd = motd,
.callback_context = context,
.pipe = pipe,
.registry = registry,
.ext_config = ext_config,
};
shell->thread =
furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, shell);
return shell;
}
void cli_shell_free(CliShell* shell) {
furi_check(shell);
furi_thread_free(shell->thread);
free(shell);
}
void cli_shell_start(CliShell* shell) {
furi_check(shell);
furi_thread_start(shell->thread);
}
void cli_shell_join(CliShell* shell) {
furi_check(shell);
furi_thread_join(shell->thread);
}
void cli_shell_set_prompt(CliShell* shell, const char* prompt) {
furi_check(shell);
shell->prompt = prompt;
}

View File

@@ -0,0 +1,75 @@
#pragma once
#include <furi.h>
#include <toolbox/pipe.h>
#include "../cli_registry.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_SHELL_STACK_SIZE (4 * 1024U)
typedef struct CliShell CliShell;
/**
* Called from the shell thread to print the Message of the Day when the shell
* is started.
*/
typedef void (*CliShellMotd)(void* context);
/**
* @brief Allocates a shell
*
* @param [in] motd Message of the Day callback
* @param [in] context Callback context
* @param [in] pipe Pipe side to be used by the shell
* @param [in] registry Command registry
* @param [in] ext_config External command configuration. See
* `CliCommandExternalConfig`. May be NULL if support for
* external commands is not required.
*
* @return Shell instance
*/
CliShell* cli_shell_alloc(
CliShellMotd motd,
void* context,
PipeSide* pipe,
CliRegistry* registry,
const CliCommandExternalConfig* ext_config);
/**
* @brief Frees a shell
*
* @param [in] shell Shell instance
*/
void cli_shell_free(CliShell* shell);
/**
* @brief Starts a shell
*
* The shell runs in a separate thread. This call is non-blocking.
*
* @param [in] shell Shell instance
*/
void cli_shell_start(CliShell* shell);
/**
* @brief Joins the shell thread
*
* @warning This call is blocking.
*
* @param [in] shell Shell instance
*/
void cli_shell_join(CliShell* shell);
/**
* @brief Sets optional text before prompt (`>:`)
*
* @param [in] shell Shell instance
*/
void cli_shell_set_prompt(CliShell* shell, const char* prompt);
#ifdef __cplusplus
}
#endif

View File

@@ -4,7 +4,7 @@ ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524
#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions)
struct CliShellCompletions {
Cli* cli;
CliRegistry* registry;
CliShell* shell;
CliShellLine* line;
CommandCompletions_t variants;
@@ -45,10 +45,11 @@ typedef struct {
// Public API
// ==========
CliShellCompletions* cli_shell_completions_alloc(Cli* cli, CliShell* shell, CliShellLine* line) {
CliShellCompletions*
cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line) {
CliShellCompletions* completions = malloc(sizeof(CliShellCompletions));
completions->cli = cli;
completions->registry = registry;
completions->shell = shell;
completions->line = line;
CommandCompletions_init(completions->variants);
@@ -108,8 +109,9 @@ void cli_shell_completions_fill_variants(CliShellCompletions* completions) {
furi_string_left(input, segment.length);
if(segment.type == CliShellCompletionSegmentTypeCommand) {
cli_lock_commands(completions->cli);
CliCommandTree_t* commands = cli_get_commands(completions->cli);
CliRegistry* registry = completions->registry;
cli_registry_lock(registry);
CliCommandTree_t* commands = cli_registry_get_commands(registry);
for
M_EACH(registered_command, *commands, CliCommandTree_t) {
FuriString* command_name = *registered_command->key_ptr;
@@ -117,7 +119,7 @@ void cli_shell_completions_fill_variants(CliShellCompletions* completions) {
CommandCompletions_push_back(completions->variants, command_name);
}
}
cli_unlock_commands(completions->cli);
cli_registry_unlock(registry);
} else {
// support removed, might reimplement in the future

View File

@@ -4,8 +4,8 @@
#include <m-array.h>
#include "cli_shell_i.h"
#include "cli_shell_line.h"
#include "../cli.h"
#include "../cli_i.h"
#include "../cli_registry.h"
#include "../cli_registry_i.h"
#ifdef __cplusplus
extern "C" {
@@ -13,7 +13,8 @@ extern "C" {
typedef struct CliShellCompletions CliShellCompletions;
CliShellCompletions* cli_shell_completions_alloc(Cli* cli, CliShell* shell, CliShellLine* line);
CliShellCompletions*
cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line);
void cli_shell_completions_free(CliShellCompletions* completions);

View File

@@ -27,6 +27,8 @@ typedef struct {
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command);
const char* cli_shell_get_prompt(CliShell* cli_shell);
#ifdef __cplusplus
}
#endif

View File

@@ -8,6 +8,7 @@ struct CliShellLine {
FuriString* history[HISTORY_DEPTH];
size_t history_entries;
CliShell* shell;
bool about_to_exit;
};
// ==========
@@ -39,14 +40,16 @@ FuriString* cli_shell_line_get_editing(CliShellLine* line) {
return line->history[0];
}
size_t cli_shell_line_prompt_length(CliShellLine* line) {
UNUSED(line);
return strlen(">: ");
}
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length) {
UNUSED(line);
snprintf(buf, length - 1, ">: ");
const char* prompt = cli_shell_get_prompt(line->shell);
snprintf(buf, length - 1, "%s>: ", prompt ? prompt : "");
}
size_t cli_shell_line_prompt_length(CliShellLine* line) {
char buffer[128];
cli_shell_line_format_prompt(line, buffer, sizeof(buffer));
return strlen(buffer);
}
void cli_shell_line_prompt(CliShellLine* line) {
@@ -65,6 +68,10 @@ void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) {
}
}
void cli_shell_line_set_about_to_exit(CliShellLine* line) {
line->about_to_exit = true;
}
size_t cli_shell_line_get_line_position(CliShellLine* line) {
return line->line_position;
}
@@ -188,8 +195,7 @@ static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
printf("\r\n");
cli_shell_execute_command(line->shell, command_copy);
furi_string_free(command_copy);
cli_shell_line_prompt(line);
if(!line->about_to_exit) cli_shell_line_prompt(line);
return true;
}
@@ -202,10 +208,13 @@ static bool cli_shell_line_input_up_down(CliKeyCombo combo, void* context) {
// print prompt with selected command
if(new_pos != line->history_position) {
char prompt[64];
cli_shell_line_format_prompt(line, prompt, sizeof(prompt));
line->history_position = new_pos;
FuriString* command = cli_shell_line_get_selected(line);
printf(
ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
ANSI_CURSOR_HOR_POS("1") "%s%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
prompt,
furi_string_get_cstr(command));
fflush(stdout);
line->line_position = furi_string_size(command);

View File

@@ -33,6 +33,8 @@ void cli_shell_line_set_line_position(CliShellLine* line, size_t position);
*/
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line);
void cli_shell_line_set_about_to_exit(CliShellLine* line);
extern CliShellKeyComboSet cli_shell_line_key_combo_set;
#ifdef __cplusplus

View File

@@ -1,9 +1,7 @@
entry,status,name,type,params
Version,+,84.1,,
Version,+,85.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_ansi.h,,
Header,+,applications/services/cli/cli_vcp.h,,
Header,+,applications/services/dialogs/dialogs.h,,
Header,+,applications/services/dolphin/dolphin.h,,
@@ -153,6 +151,10 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,,
Header,+,lib/toolbox/api_lock.h,,
Header,+,lib/toolbox/args.h,,
Header,+,lib/toolbox/bit_buffer.h,,
Header,+,lib/toolbox/cli/cli_ansi.h,,
Header,+,lib/toolbox/cli/cli_command.h,,
Header,+,lib/toolbox/cli/cli_registry.h,,
Header,+,lib/toolbox/cli/shell/cli_shell.h,,
Header,+,lib/toolbox/compress.h,,
Header,+,lib/toolbox/crc32_calc.h,,
Header,+,lib/toolbox/dir_walk.h,,
@@ -779,17 +781,24 @@ Function,-,ceill,long double,long double
Function,-,cfree,void,void*
Function,-,clearerr,void,FILE*
Function,-,clearerr_unlocked,void,FILE*
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*"
Function,+,cli_add_command_ex,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t"
Function,+,cli_ansi_parser_alloc,CliAnsiParser*,
Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
Function,+,cli_delete_command,void,"Cli*, const char*"
Function,+,cli_enumerate_external_commands,void,Cli*
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
Function,+,cli_remove_external_commands,void,Cli*
Function,+,cli_registry_add_command,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*"
Function,+,cli_registry_add_command_ex,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t"
Function,+,cli_registry_alloc,CliRegistry*,
Function,+,cli_registry_delete_command,void,"CliRegistry*, const char*"
Function,+,cli_registry_free,void,CliRegistry*
Function,+,cli_registry_reload_external_commands,void,"CliRegistry*, const CliCommandExternalConfig*"
Function,+,cli_registry_remove_external_commands,void,CliRegistry*
Function,+,cli_shell_alloc,CliShell*,"CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*"
Function,+,cli_shell_free,void,CliShell*
Function,+,cli_shell_join,void,CliShell*
Function,+,cli_shell_set_prompt,void,"CliShell*, const char*"
Function,+,cli_shell_start,void,CliShell*
Function,+,cli_vcp_disable,void,CliVcp*
Function,+,cli_vcp_enable,void,CliVcp*
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
1 entry status name type params
2 Version + 84.1 85.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
Header + applications/services/cli/cli.h
Header + applications/services/cli/cli_ansi.h
5 Header + applications/services/cli/cli_vcp.h
6 Header + applications/services/dialogs/dialogs.h
7 Header + applications/services/dolphin/dolphin.h
151 Header + lib/toolbox/api_lock.h
152 Header + lib/toolbox/args.h
153 Header + lib/toolbox/bit_buffer.h
154 Header + lib/toolbox/cli/cli_ansi.h
155 Header + lib/toolbox/cli/cli_command.h
156 Header + lib/toolbox/cli/cli_registry.h
157 Header + lib/toolbox/cli/shell/cli_shell.h
158 Header + lib/toolbox/compress.h
159 Header + lib/toolbox/crc32_calc.h
160 Header + lib/toolbox/dir_walk.h
781 Function - cfree void void*
782 Function - clearerr void FILE*
783 Function - clearerr_unlocked void FILE*
Function + cli_add_command void Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*
Function + cli_add_command_ex void Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t
784 Function + cli_ansi_parser_alloc CliAnsiParser*
785 Function + cli_ansi_parser_feed CliAnsiParserResult CliAnsiParser*, char
786 Function + cli_ansi_parser_feed_timeout CliAnsiParserResult CliAnsiParser*
787 Function + cli_ansi_parser_free void CliAnsiParser*
Function + cli_delete_command void Cli*, const char*
Function + cli_enumerate_external_commands void Cli*
788 Function + cli_is_pipe_broken_or_is_etx_next_char _Bool PipeSide*
789 Function + cli_print_usage void const char*, const char*, const char*
790 Function + cli_remove_external_commands cli_registry_add_command void Cli* CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*
791 Function + cli_registry_add_command_ex void CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t
792 Function + cli_registry_alloc CliRegistry*
793 Function + cli_registry_delete_command void CliRegistry*, const char*
794 Function + cli_registry_free void CliRegistry*
795 Function + cli_registry_reload_external_commands void CliRegistry*, const CliCommandExternalConfig*
796 Function + cli_registry_remove_external_commands void CliRegistry*
797 Function + cli_shell_alloc CliShell* CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*
798 Function + cli_shell_free void CliShell*
799 Function + cli_shell_join void CliShell*
800 Function + cli_shell_set_prompt void CliShell*, const char*
801 Function + cli_shell_start void CliShell*
802 Function + cli_vcp_disable void CliVcp*
803 Function + cli_vcp_enable void CliVcp*
804 Function + composite_api_resolver_add void CompositeApiResolver*, const ElfApiInterface*

View File

@@ -1,10 +1,8 @@
entry,status,name,type,params
Version,+,84.1,,
Version,+,85.0,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_ansi.h,,
Header,+,applications/services/cli/cli_vcp.h,,
Header,+,applications/services/dialogs/dialogs.h,,
Header,+,applications/services/dolphin/dolphin.h,,
@@ -225,6 +223,10 @@ Header,+,lib/subghz/transmitter.h,,
Header,+,lib/toolbox/api_lock.h,,
Header,+,lib/toolbox/args.h,,
Header,+,lib/toolbox/bit_buffer.h,,
Header,+,lib/toolbox/cli/cli_ansi.h,,
Header,+,lib/toolbox/cli/cli_command.h,,
Header,+,lib/toolbox/cli/cli_registry.h,,
Header,+,lib/toolbox/cli/shell/cli_shell.h,,
Header,+,lib/toolbox/compress.h,,
Header,+,lib/toolbox/crc32_calc.h,,
Header,+,lib/toolbox/dir_walk.h,,
@@ -856,17 +858,24 @@ Function,-,ceill,long double,long double
Function,-,cfree,void,void*
Function,-,clearerr,void,FILE*
Function,-,clearerr_unlocked,void,FILE*
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*"
Function,+,cli_add_command_ex,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t"
Function,+,cli_ansi_parser_alloc,CliAnsiParser*,
Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
Function,+,cli_delete_command,void,"Cli*, const char*"
Function,+,cli_enumerate_external_commands,void,Cli*
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
Function,+,cli_remove_external_commands,void,Cli*
Function,+,cli_registry_add_command,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*"
Function,+,cli_registry_add_command_ex,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t"
Function,+,cli_registry_alloc,CliRegistry*,
Function,+,cli_registry_delete_command,void,"CliRegistry*, const char*"
Function,+,cli_registry_free,void,CliRegistry*
Function,+,cli_registry_reload_external_commands,void,"CliRegistry*, const CliCommandExternalConfig*"
Function,+,cli_registry_remove_external_commands,void,CliRegistry*
Function,+,cli_shell_alloc,CliShell*,"CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*"
Function,+,cli_shell_free,void,CliShell*
Function,+,cli_shell_join,void,CliShell*
Function,+,cli_shell_set_prompt,void,"CliShell*, const char*"
Function,+,cli_shell_start,void,CliShell*
Function,+,cli_vcp_disable,void,CliVcp*
Function,+,cli_vcp_enable,void,CliVcp*
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
1 entry status name type params
2 Version + 84.1 85.0
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
Header + applications/services/cli/cli.h
Header + applications/services/cli/cli_ansi.h
6 Header + applications/services/cli/cli_vcp.h
7 Header + applications/services/dialogs/dialogs.h
8 Header + applications/services/dolphin/dolphin.h
223 Header + lib/toolbox/api_lock.h
224 Header + lib/toolbox/args.h
225 Header + lib/toolbox/bit_buffer.h
226 Header + lib/toolbox/cli/cli_ansi.h
227 Header + lib/toolbox/cli/cli_command.h
228 Header + lib/toolbox/cli/cli_registry.h
229 Header + lib/toolbox/cli/shell/cli_shell.h
230 Header + lib/toolbox/compress.h
231 Header + lib/toolbox/crc32_calc.h
232 Header + lib/toolbox/dir_walk.h
858 Function - cfree void void*
859 Function - clearerr void FILE*
860 Function - clearerr_unlocked void FILE*
Function + cli_add_command void Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*
Function + cli_add_command_ex void Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t
861 Function + cli_ansi_parser_alloc CliAnsiParser*
862 Function + cli_ansi_parser_feed CliAnsiParserResult CliAnsiParser*, char
863 Function + cli_ansi_parser_feed_timeout CliAnsiParserResult CliAnsiParser*
864 Function + cli_ansi_parser_free void CliAnsiParser*
Function + cli_delete_command void Cli*, const char*
Function + cli_enumerate_external_commands void Cli*
865 Function + cli_is_pipe_broken_or_is_etx_next_char _Bool PipeSide*
866 Function + cli_print_usage void const char*, const char*, const char*
867 Function + cli_remove_external_commands cli_registry_add_command void Cli* CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*
868 Function + cli_registry_add_command_ex void CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t
869 Function + cli_registry_alloc CliRegistry*
870 Function + cli_registry_delete_command void CliRegistry*, const char*
871 Function + cli_registry_free void CliRegistry*
872 Function + cli_registry_reload_external_commands void CliRegistry*, const CliCommandExternalConfig*
873 Function + cli_registry_remove_external_commands void CliRegistry*
874 Function + cli_shell_alloc CliShell* CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*
875 Function + cli_shell_free void CliShell*
876 Function + cli_shell_join void CliShell*
877 Function + cli_shell_set_prompt void CliShell*, const char*
878 Function + cli_shell_start void CliShell*
879 Function + cli_vcp_disable void CliVcp*
880 Function + cli_vcp_enable void CliVcp*
881 Function + composite_api_resolver_add void CompositeApiResolver*, const ElfApiInterface*