mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-07 19:01:54 -07:00
Merge remote-tracking branch 'ofw/dev' into mntm-dev
This commit is contained in:
+4
-1
@@ -93,7 +93,10 @@
|
||||
- Infrared:
|
||||
- OFW: Add Fujitsu ASTG12LVCC to AC Universal Remote (by @KereruA0i)
|
||||
- OFW: Increase max carrier limit to 1000000 (by @skotopes)
|
||||
- OFW: CLI: New CLI architecture, some text formatting, better stability and less RAM usage (by @portasynthinca3)
|
||||
- CLI:
|
||||
- OFW: New CLI architecture, some text formatting, better stability and less RAM usage (by @portasynthinca3)
|
||||
- OFW: Autocomplete and more keyboard shortcuts (by @portasynthinca3)
|
||||
- OFW: Improved loading of CLI commands from SD card with fals and threads (by @portasynthinca3)
|
||||
- OFW: Power: Added OTG controls to Power service, remembers OTG when unplugging USB (by @Astrrra & @skotopes)
|
||||
- OFW: GUI: Updated Button Panel with more options for button handling (by @Akiva-Cohen)
|
||||
- Furi:
|
||||
|
||||
@@ -4,7 +4,7 @@ App(
|
||||
entry_point="unit_tests_on_system_start",
|
||||
sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"],
|
||||
cdefines=["APP_UNIT_TESTS"],
|
||||
requires=["system_settings", "subghz_start"],
|
||||
requires=["system_settings", "cli_subghz"],
|
||||
provides=["delay_test"],
|
||||
resources="resources",
|
||||
order=100,
|
||||
|
||||
@@ -70,10 +70,9 @@ static void on_data_arrived(PipeSide* pipe, void* context) {
|
||||
}
|
||||
|
||||
static void on_space_freed(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagSpaceFreed;
|
||||
const char* message = "Hi!";
|
||||
pipe_send(pipe, message, strlen(message));
|
||||
}
|
||||
|
||||
static void on_became_broken(PipeSide* pipe, void* context) {
|
||||
@@ -119,16 +118,10 @@ MU_TEST(pipe_test_event_loop) {
|
||||
size_t size = pipe_receive(alice, buffer_1, strlen(message));
|
||||
buffer_1[size] = 0;
|
||||
|
||||
char buffer_2[16];
|
||||
const char* expected_reply = "Hi!";
|
||||
size = pipe_receive(alice, buffer_2, strlen(expected_reply));
|
||||
buffer_2[size] = 0;
|
||||
|
||||
pipe_free(alice);
|
||||
furi_thread_join(thread);
|
||||
|
||||
mu_assert_string_eq(message, buffer_1);
|
||||
mu_assert_string_eq(expected_reply, buffer_2);
|
||||
mu_assert_int_eq(
|
||||
TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken,
|
||||
furi_thread_get_return_code(thread));
|
||||
|
||||
@@ -23,12 +23,6 @@ App(
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"cli",
|
||||
"ibutton_start",
|
||||
"onewire_start",
|
||||
"subghz_start",
|
||||
"subghz_load_extended_settings",
|
||||
"infrared_start",
|
||||
"lfrfid_start",
|
||||
"nfc_start",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ App(
|
||||
icon="A_BadUsb_14",
|
||||
order=70,
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Tools",
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
icon="A_GPIO_14",
|
||||
order=50,
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="GPIO",
|
||||
)
|
||||
|
||||
@@ -7,24 +7,16 @@ App(
|
||||
icon="A_iButton_14",
|
||||
stack_size=2 * 1024,
|
||||
order=60,
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="iButton",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="ibutton_cli",
|
||||
appid="cli_ikey",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="ibutton_cli_plugin_ep",
|
||||
entry_point="cli_ikey_ep",
|
||||
requires=["cli"],
|
||||
sources=["ibutton_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="ibutton_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
targets=["f7"],
|
||||
entry_point="ibutton_on_system_start",
|
||||
sources=["ibutton_cli.c"],
|
||||
order=60,
|
||||
)
|
||||
|
||||
@@ -204,7 +204,7 @@ void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
};
|
||||
|
||||
} while(false);
|
||||
|
||||
@@ -216,8 +216,7 @@ void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
ibutton_protocols_free(protocols);
|
||||
}
|
||||
|
||||
void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -241,15 +240,4 @@ void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("ibutton", ibutton_cli)
|
||||
|
||||
void ibutton_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(ibutton_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024);
|
||||
|
||||
@@ -9,15 +9,16 @@ App(
|
||||
order=40,
|
||||
sources=["*.c", "!infrared_cli.c"],
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Infrared",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="infrared_cli",
|
||||
appid="cli_ir",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="infrared_cli_start_ir_plugin_ep",
|
||||
entry_point="cli_ir_ep",
|
||||
requires=["cli"],
|
||||
sources=[
|
||||
"infrared_cli.c",
|
||||
@@ -25,12 +26,3 @@ App(
|
||||
"infrared_signal.c",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="infrared_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
targets=["f7"],
|
||||
entry_point="infrared_on_system_start",
|
||||
sources=["infrared_cli.c"],
|
||||
order=20,
|
||||
)
|
||||
|
||||
@@ -526,7 +526,7 @@ static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) {
|
||||
furi_string_free(arg2);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_infrared_is_busy()) {
|
||||
printf("INFRARED is busy. Exiting.");
|
||||
@@ -554,15 +554,4 @@ static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* contex
|
||||
furi_string_free(command);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("infrared", infrared_cli_start_ir)
|
||||
|
||||
void infrared_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(infrared_cli_start_ir);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048);
|
||||
|
||||
@@ -7,24 +7,16 @@ App(
|
||||
icon="A_125khz_14",
|
||||
stack_size=2 * 1024,
|
||||
order=20,
|
||||
fap_libs=["assets"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="RFID",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="lfrfid_cli",
|
||||
appid="cli_rfid",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="lfrfid_cli_plugin_ep",
|
||||
entry_point="cli_rfid_ep",
|
||||
requires=["cli"],
|
||||
sources=["lfrfid_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="lfrfid_start",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="lfrfid_on_system_start",
|
||||
sources=["lfrfid_cli.c"],
|
||||
order=50,
|
||||
)
|
||||
|
||||
@@ -536,7 +536,7 @@ static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) {
|
||||
furi_string_free(filepath);
|
||||
}
|
||||
|
||||
static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -566,11 +566,4 @@ static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("lfrfid", lfrfid_cli)
|
||||
|
||||
void lfrfid_on_system_start(void) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048);
|
||||
|
||||
@@ -350,19 +350,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_cli",
|
||||
appid="cli_nfc",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="nfc_cli_plugin_ep",
|
||||
entry_point="cli_nfc_ep",
|
||||
requires=["cli"],
|
||||
sources=["nfc_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="nfc_on_system_start",
|
||||
sources=["nfc_cli.c"],
|
||||
order=30,
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ static void nfc_cli_field(PipeSide* pipe, FuriString* args) {
|
||||
furi_hal_nfc_release();
|
||||
}
|
||||
|
||||
static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -65,15 +65,4 @@ static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("nfc", nfc_cli)
|
||||
|
||||
void nfc_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(nfc_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024);
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
App(
|
||||
appid="onewire_cli",
|
||||
appid="cli_onewire",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="onewire_cli_plugin_ep",
|
||||
entry_point="cli_onewire_ep",
|
||||
requires=["cli"],
|
||||
sources=["onewire_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="onewire_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="onewire_on_system_start",
|
||||
sources=["onewire_cli.c"],
|
||||
order=60,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli_commands.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
@@ -45,7 +46,7 @@ static void onewire_cli_search(PipeSide* pipe) {
|
||||
furi_record_close(RECORD_POWER);
|
||||
}
|
||||
|
||||
static void onewire_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -63,15 +64,4 @@ static void onewire_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("onewire", onewire_cli)
|
||||
|
||||
void onewire_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(onewire_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024);
|
||||
|
||||
@@ -18,7 +18,7 @@ App(
|
||||
# ],
|
||||
requires=["region"],
|
||||
resources="resources",
|
||||
fap_libs=["hwdrivers"],
|
||||
fap_libs=["assets", "hwdrivers"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Sub-GHz",
|
||||
sdk_headers=["subghz_fap.h"],
|
||||
@@ -45,24 +45,14 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="subghz_cli",
|
||||
appid="cli_subghz",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="subghz_cli_command_plugin_ep",
|
||||
entry_point="cli_subghz_ep",
|
||||
requires=["cli"],
|
||||
sources=["subghz_cli.c", "helpers/subghz_chat.c"],
|
||||
)
|
||||
|
||||
|
||||
App(
|
||||
appid="subghz_start",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="subghz_on_system_start",
|
||||
# sources=["subghz_cli.c"],
|
||||
order=40,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="subghz_load_extended_settings",
|
||||
targets=["f7"],
|
||||
|
||||
@@ -1115,7 +1115,7 @@ static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) {
|
||||
printf("\r\nExit chat\r\n");
|
||||
}
|
||||
|
||||
static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
FuriString* cmd = furi_string_alloc();
|
||||
|
||||
do {
|
||||
@@ -1182,24 +1182,4 @@ static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context)
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("subghz", subghz_cli_command)
|
||||
|
||||
static void subghz_cli_command_chat_wrapper(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_replace_at(args, 0, 0, "chat ");
|
||||
subghz_cli_command_wrapper(pipe, args, context);
|
||||
}
|
||||
|
||||
void subghz_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
|
||||
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command_wrapper, NULL);
|
||||
cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat_wrapper, NULL);
|
||||
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(subghz_cli_command);
|
||||
UNUSED(subghz_cli_command_chat_wrapper);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048);
|
||||
|
||||
@@ -7,7 +7,7 @@ App(
|
||||
icon="A_U2F_14",
|
||||
order=80,
|
||||
resources="resources",
|
||||
fap_libs=["mbedtls"],
|
||||
fap_libs=["assets", "mbedtls"],
|
||||
fap_category="USB",
|
||||
fap_icon="icon.png",
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ App(
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"cli_vcp",
|
||||
"crypto_start",
|
||||
"rpc_start",
|
||||
"expansion_start",
|
||||
"bt",
|
||||
|
||||
@@ -9,7 +9,6 @@ App(
|
||||
"dialogs",
|
||||
],
|
||||
provides=[
|
||||
"bt_start",
|
||||
"bt_settings",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
@@ -18,17 +17,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="bt_cli",
|
||||
appid="cli_bt",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="bt_cli_plugin_ep",
|
||||
entry_point="cli_bt_ep",
|
||||
requires=["cli"],
|
||||
sources=["bt_cli.c", "bt_service/bt_settings_api.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="bt_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="bt_on_system_start",
|
||||
order=70,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#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>
|
||||
|
||||
@@ -180,7 +181,7 @@ static void bt_cli_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
|
||||
@@ -228,15 +229,4 @@ static void bt_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("bt", bt_cli)
|
||||
|
||||
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_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(bt_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(bt, execute, CliCommandFlagDefault, 1024);
|
||||
|
||||
@@ -6,6 +6,7 @@ App(
|
||||
sources=[
|
||||
"cli.c",
|
||||
"shell/cli_shell.c",
|
||||
"shell/cli_shell_completions.c",
|
||||
"shell/cli_shell_line.c",
|
||||
"cli_commands.c",
|
||||
"cli_command_gpio.c",
|
||||
@@ -34,82 +35,107 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="src_cli",
|
||||
appid="cli_hello_world",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_hello_world_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/hello_world.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_neofetch",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_neofetch_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/neofetch.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_src",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_src_plugin_ep",
|
||||
entry_point="cli_src_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="neofetch_cli",
|
||||
appid="cli_uptime",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_neofetch_plugin_ep",
|
||||
entry_point="cli_uptime_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="uptime_cli",
|
||||
appid="cli_date",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_uptime_plugin_ep",
|
||||
entry_point="cli_date_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="date_cli",
|
||||
appid="cli_sysctl",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_date_plugin_ep",
|
||||
entry_point="cli_sysctl_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="sysctl_cli",
|
||||
appid="cli_top",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_sysctl_plugin_ep",
|
||||
entry_point="cli_top_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="vibro_cli",
|
||||
appid="cli_vibro",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_vibro_plugin_ep",
|
||||
entry_point="cli_vibro_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="led_cli",
|
||||
appid="cli_led",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_led_plugin_ep",
|
||||
entry_point="cli_led_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="gpio_cli",
|
||||
appid="cli_gpio",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_gpio_plugin_ep",
|
||||
entry_point="cli_gpio_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c", "cli_command_gpio.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="i2c_cli",
|
||||
appid="cli_i2c",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_i2c_plugin_ep",
|
||||
entry_point="cli_i2c_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_clear",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_clear_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
#include "cli_ansi.h"
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TAG "cli"
|
||||
|
||||
struct Cli {
|
||||
@@ -19,7 +14,7 @@ struct Cli {
|
||||
Cli* cli_alloc(void) {
|
||||
Cli* cli = malloc(sizeof(Cli));
|
||||
CliCommandTree_init(cli->commands);
|
||||
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
cli->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
||||
return cli;
|
||||
}
|
||||
|
||||
@@ -43,6 +38,9 @@ void cli_add_command_ex(
|
||||
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
|
||||
@@ -91,18 +89,75 @@ bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) {
|
||||
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_assert(cli);
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void cli_unlock_commands(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
furi_mutex_release(cli->mutex);
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
CliCommandTree_t* cli_get_commands(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
furi_check(cli);
|
||||
furi_check(furi_mutex_get_owner(cli->mutex) == furi_thread_get_current_id());
|
||||
return &cli->commands;
|
||||
}
|
||||
|
||||
@@ -124,24 +179,6 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
||||
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);
|
||||
}
|
||||
|
||||
void cli_plugin_wrapper(const char* name, PipeSide* pipe, FuriString* args, void* context) {
|
||||
PluginManager* manager =
|
||||
plugin_manager_alloc(CLI_PLUGIN_APP_ID, CLI_PLUGIN_API_VERSION, firmware_api_interface);
|
||||
FuriString* path =
|
||||
furi_string_alloc_printf(EXT_PATH("apps_data/cli/plugins/%s_cli.fal"), name);
|
||||
PluginManagerError error = plugin_manager_load_single(manager, furi_string_get_cstr(path));
|
||||
if(error == PluginManagerErrorNone) {
|
||||
const CliExecuteCallback handler = plugin_manager_get_ep(manager, 0);
|
||||
handler(pipe, args, context);
|
||||
} else {
|
||||
printf(
|
||||
"CLI plugin '%s' failed (code %" PRIu16 "), reinstall firmware or check logs\r\n",
|
||||
name,
|
||||
error);
|
||||
}
|
||||
furi_string_free(path);
|
||||
plugin_manager_free(manager);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ typedef enum {
|
||||
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 */
|
||||
@@ -87,6 +94,20 @@ void cli_add_command_ex(
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
|
||||
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
|
||||
@@ -57,158 +56,6 @@ void cli_command_info(PipeSide* pipe, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Lil Easter egg :>
|
||||
void cli_command_neofetch(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
static const char* const neofetch_logo[] = {
|
||||
" _.-------.._ -,",
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ ",
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |",
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;",
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
|
||||
" | | | 0 | | .-' ,/` /",
|
||||
" | ,..\\ \\ ,.-\"` ,/` /",
|
||||
"; : `/`\"\"\\` ,/--==,/-----,",
|
||||
"| `-...| -.___-Z:_______J...---;",
|
||||
": ` _-'",
|
||||
};
|
||||
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
|
||||
|
||||
// Determine logo parameters
|
||||
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
|
||||
for(size_t i = 0; i < logo_height; i++)
|
||||
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
|
||||
logo_width += 4; // space between logo and info
|
||||
|
||||
// Format hostname delimiter
|
||||
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
|
||||
char delimiter[64];
|
||||
memset(delimiter, '-', size_of_hostname);
|
||||
delimiter[size_of_hostname] = '\0';
|
||||
|
||||
// Get heap info
|
||||
size_t heap_total = memmgr_get_total_heap();
|
||||
size_t heap_used = heap_total - memmgr_get_free_heap();
|
||||
uint16_t heap_percent = (100 * heap_used) / heap_total;
|
||||
|
||||
// Get storage info
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
uint64_t ext_total, ext_free, ext_used, ext_percent;
|
||||
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
|
||||
ext_used = ext_total - ext_free;
|
||||
ext_percent = (100 * ext_used) / ext_total;
|
||||
ext_used /= 1024 * 1024;
|
||||
ext_total /= 1024 * 1024;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// Get battery info
|
||||
uint16_t charge_percent = furi_hal_power_get_pct();
|
||||
const char* charge_state;
|
||||
if(furi_hal_power_is_charging()) {
|
||||
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
|
||||
charge_state = "charging";
|
||||
} else {
|
||||
charge_state = "charged";
|
||||
}
|
||||
} else {
|
||||
charge_state = "discharging";
|
||||
}
|
||||
|
||||
// Get misc info
|
||||
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
|
||||
const Version* version = version_get();
|
||||
uint16_t major, minor;
|
||||
furi_hal_info_get_api_version(&major, &minor);
|
||||
|
||||
// Print ASCII art with info
|
||||
const size_t info_height = 16;
|
||||
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
|
||||
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
|
||||
switch(i) {
|
||||
case 0: // you@<hostname>
|
||||
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
|
||||
break;
|
||||
case 1: // delimiter
|
||||
printf(ANSI_RESET "%s", delimiter);
|
||||
break;
|
||||
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
|
||||
printf(
|
||||
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
|
||||
version_get_version(version),
|
||||
version_get_gitbranch(version),
|
||||
version_get_version(version),
|
||||
version_get_githash(version),
|
||||
major,
|
||||
minor);
|
||||
break;
|
||||
case 3: // Host: <model> <hostname>
|
||||
printf(
|
||||
"Host" ANSI_RESET ": %s %s",
|
||||
furi_hal_version_get_model_code(),
|
||||
furi_hal_version_get_device_name_ptr());
|
||||
break;
|
||||
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
|
||||
printf(
|
||||
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
|
||||
tskKERNEL_VERSION_MAJOR,
|
||||
tskKERNEL_VERSION_MINOR,
|
||||
tskKERNEL_VERSION_BUILD);
|
||||
break;
|
||||
case 5: // Uptime: ?h?m?s
|
||||
printf(
|
||||
"Uptime" ANSI_RESET ": %luh%lum%lus",
|
||||
uptime / 60 / 60,
|
||||
uptime / 60 % 60,
|
||||
uptime % 60);
|
||||
break;
|
||||
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
|
||||
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
|
||||
break;
|
||||
case 7: // DE: GuiSrv
|
||||
printf("DE" ANSI_RESET ": GuiSrv");
|
||||
break;
|
||||
case 8: // Shell: CliSrv
|
||||
printf("Shell" ANSI_RESET ": CliSrv");
|
||||
break;
|
||||
case 9: // CPU: STM32WB55RG @ 64 MHz
|
||||
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
|
||||
break;
|
||||
case 10: // Memory: <used> / <total> B (??%)
|
||||
printf(
|
||||
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
|
||||
break;
|
||||
case 11: // Disk (/ext): <used> / <total> MiB (??%)
|
||||
printf(
|
||||
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
|
||||
ext_used,
|
||||
ext_total,
|
||||
ext_percent);
|
||||
break;
|
||||
case 12: // Battery: ??% (<state>)
|
||||
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
|
||||
break;
|
||||
case 13: // empty space
|
||||
break;
|
||||
case 14: // Colors (line 1)
|
||||
for(size_t j = 30; j <= 37; j++)
|
||||
printf("\e[%dm███", j);
|
||||
break;
|
||||
case 15: // Colors (line 2)
|
||||
for(size_t j = 90; j <= 97; j++)
|
||||
printf("\e[%dm███", j);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
printf(ANSI_RESET);
|
||||
#undef NEOFETCH_COLOR
|
||||
}
|
||||
|
||||
void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
@@ -244,6 +91,8 @@ void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -718,6 +567,16 @@ 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)
|
||||
*/
|
||||
@@ -746,44 +605,29 @@ void cli_command_clear(PipeSide* pipe, FuriString* args, void* context) {
|
||||
printf("\e[2J\e[H");
|
||||
}
|
||||
|
||||
CLI_PLUGIN_WRAPPER("src", cli_command_src)
|
||||
CLI_PLUGIN_WRAPPER("neofetch", cli_command_neofetch)
|
||||
CLI_PLUGIN_WRAPPER("uptime", cli_command_uptime)
|
||||
CLI_PLUGIN_WRAPPER("date", cli_command_date)
|
||||
CLI_PLUGIN_WRAPPER("sysctl", cli_command_sysctl)
|
||||
CLI_PLUGIN_WRAPPER("vibro", cli_command_vibro)
|
||||
CLI_PLUGIN_WRAPPER("led", cli_command_led)
|
||||
CLI_PLUGIN_WRAPPER("gpio", cli_command_gpio)
|
||||
CLI_PLUGIN_WRAPPER("i2c", cli_command_i2c)
|
||||
CLI_PLUGIN_WRAPPER("clear", cli_command_clear)
|
||||
|
||||
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, "source", CliCommandFlagParallelSafe, cli_command_src_wrapper, NULL);
|
||||
cli_add_command(cli, "src", CliCommandFlagParallelSafe, cli_command_src_wrapper, NULL);
|
||||
cli_add_command(
|
||||
cli, "neofetch", CliCommandFlagParallelSafe, cli_command_neofetch_wrapper, NULL);
|
||||
cli, "reload_ext_cmds", CliCommandFlagDefault, cli_command_reload_external, NULL);
|
||||
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
|
||||
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime_wrapper, NULL);
|
||||
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date_wrapper, NULL);
|
||||
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_add_command(cli, "l", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl_wrapper, 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_wrapper, NULL);
|
||||
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led_wrapper, NULL);
|
||||
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio_wrapper, NULL);
|
||||
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c_wrapper, NULL);
|
||||
|
||||
cli_add_command(cli, "clear", CliCommandFlagParallelSafe, cli_command_clear, NULL);
|
||||
cli_add_command(cli, "cls", CliCommandFlagParallelSafe, cli_command_clear, 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);
|
||||
|
||||
@@ -23,12 +23,12 @@ typedef struct {
|
||||
stack_depth, \
|
||||
}; \
|
||||
\
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = { \
|
||||
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; \
|
||||
return &plugin_descriptor_##name; \
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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
|
||||
@@ -33,7 +34,8 @@ BPTREE_DEF2(
|
||||
FURI_STRING_OPLIST,
|
||||
CliCommand,
|
||||
M_POD_OPLIST);
|
||||
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, 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);
|
||||
|
||||
@@ -46,29 +48,6 @@ void cli_unlock_commands(Cli* cli);
|
||||
*/
|
||||
CliCommandTree_t* cli_get_commands(Cli* cli);
|
||||
|
||||
// CLI command wrapping to load from plugin file on SD card
|
||||
// Just need to:
|
||||
// - Use CLI_PLUGIN_WRAPPER("name", cmd_callback)
|
||||
// - Replace callback usages with cmd_callback_wrapper
|
||||
// - Add "name_cli" entry in app manifest to build as plugin
|
||||
void cli_plugin_wrapper(const char* name, PipeSide* pipe, FuriString* args, void* context);
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#define CLI_PLUGIN_APP_ID "cli"
|
||||
#define CLI_PLUGIN_API_VERSION 1
|
||||
#define CLI_PLUGIN_WRAPPER(plugin_name_without_cli_suffix, cli_command_callback) \
|
||||
void cli_command_callback##_wrapper(PipeSide* pipe, FuriString* args, void* context) { \
|
||||
cli_plugin_wrapper(plugin_name_without_cli_suffix, pipe, args, context); \
|
||||
} \
|
||||
static const FlipperAppPluginDescriptor cli_command_callback##_plugin_descriptor = { \
|
||||
.appid = CLI_PLUGIN_APP_ID, \
|
||||
.ep_api_version = CLI_PLUGIN_API_VERSION, \
|
||||
.entry_point = &cli_command_callback, \
|
||||
}; \
|
||||
const FlipperAppPluginDescriptor* cli_command_callback##_plugin_ep(void) { \
|
||||
UNUSED(cli_command_callback##_wrapper); \
|
||||
return &cli_command_callback##_plugin_descriptor; \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "../cli_commands.h"
|
||||
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
puts("Hello, World!");
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagDefault, 768);
|
||||
@@ -0,0 +1,159 @@
|
||||
#include "../cli_commands.h"
|
||||
#include <toolbox/version.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_info.h>
|
||||
#include <FreeRTOS.h>
|
||||
#include <FreeRTOS-Kernel/include/task.h>
|
||||
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
static const char* const neofetch_logo[] = {
|
||||
" _.-------.._ -,",
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ ",
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |",
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;",
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
|
||||
" | | | 0 | | .-' ,/` /",
|
||||
" | ,..\\ \\ ,.-\"` ,/` /",
|
||||
"; : `/`\"\"\\` ,/--==,/-----,",
|
||||
"| `-...| -.___-Z:_______J...---;",
|
||||
": ` _-'",
|
||||
};
|
||||
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
|
||||
|
||||
// Determine logo parameters
|
||||
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
|
||||
for(size_t i = 0; i < logo_height; i++)
|
||||
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
|
||||
logo_width += 4; // space between logo and info
|
||||
|
||||
// Format hostname delimiter
|
||||
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
|
||||
char delimiter[64];
|
||||
memset(delimiter, '-', size_of_hostname);
|
||||
delimiter[size_of_hostname] = '\0';
|
||||
|
||||
// Get heap info
|
||||
size_t heap_total = memmgr_get_total_heap();
|
||||
size_t heap_used = heap_total - memmgr_get_free_heap();
|
||||
uint16_t heap_percent = (100 * heap_used) / heap_total;
|
||||
|
||||
// Get storage info
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
uint64_t ext_total, ext_free, ext_used, ext_percent;
|
||||
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
|
||||
ext_used = ext_total - ext_free;
|
||||
ext_percent = (100 * ext_used) / ext_total;
|
||||
ext_used /= 1024 * 1024;
|
||||
ext_total /= 1024 * 1024;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// Get battery info
|
||||
uint16_t charge_percent = furi_hal_power_get_pct();
|
||||
const char* charge_state;
|
||||
if(furi_hal_power_is_charging()) {
|
||||
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
|
||||
charge_state = "charging";
|
||||
} else {
|
||||
charge_state = "charged";
|
||||
}
|
||||
} else {
|
||||
charge_state = "discharging";
|
||||
}
|
||||
|
||||
// Get misc info
|
||||
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
|
||||
const Version* version = version_get();
|
||||
uint16_t major, minor;
|
||||
furi_hal_info_get_api_version(&major, &minor);
|
||||
|
||||
// Print ASCII art with info
|
||||
const size_t info_height = 16;
|
||||
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
|
||||
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
|
||||
switch(i) {
|
||||
case 0: // you@<hostname>
|
||||
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
|
||||
break;
|
||||
case 1: // delimiter
|
||||
printf(ANSI_RESET "%s", delimiter);
|
||||
break;
|
||||
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
|
||||
printf(
|
||||
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
|
||||
version_get_version(version),
|
||||
version_get_gitbranch(version),
|
||||
version_get_version(version),
|
||||
version_get_githash(version),
|
||||
major,
|
||||
minor);
|
||||
break;
|
||||
case 3: // Host: <model> <hostname>
|
||||
printf(
|
||||
"Host" ANSI_RESET ": %s %s",
|
||||
furi_hal_version_get_model_code(),
|
||||
furi_hal_version_get_device_name_ptr());
|
||||
break;
|
||||
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
|
||||
printf(
|
||||
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
|
||||
tskKERNEL_VERSION_MAJOR,
|
||||
tskKERNEL_VERSION_MINOR,
|
||||
tskKERNEL_VERSION_BUILD);
|
||||
break;
|
||||
case 5: // Uptime: ?h?m?s
|
||||
printf(
|
||||
"Uptime" ANSI_RESET ": %luh%lum%lus",
|
||||
uptime / 60 / 60,
|
||||
uptime / 60 % 60,
|
||||
uptime % 60);
|
||||
break;
|
||||
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
|
||||
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
|
||||
break;
|
||||
case 7: // DE: GuiSrv
|
||||
printf("DE" ANSI_RESET ": GuiSrv");
|
||||
break;
|
||||
case 8: // Shell: CliSrv
|
||||
printf("Shell" ANSI_RESET ": CliShell");
|
||||
break;
|
||||
case 9: // CPU: STM32WB55RG @ 64 MHz
|
||||
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
|
||||
break;
|
||||
case 10: // Memory: <used> / <total> B (??%)
|
||||
printf(
|
||||
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
|
||||
break;
|
||||
case 11: // Disk (/ext): <used> / <total> MiB (??%)
|
||||
printf(
|
||||
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
|
||||
ext_used,
|
||||
ext_total,
|
||||
ext_percent);
|
||||
break;
|
||||
case 12: // Battery: ??% (<state>)
|
||||
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
|
||||
break;
|
||||
case 13: // empty space
|
||||
break;
|
||||
case 14: // Colors (line 1)
|
||||
for(size_t j = 30; j <= 37; j++)
|
||||
printf("\e[%dm███", j);
|
||||
break;
|
||||
case 15: // Colors (line 2)
|
||||
for(size_t j = 90; j <= 97; j++)
|
||||
printf("\e[%dm███", j);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
printf(ANSI_RESET);
|
||||
#undef NEOFETCH_COLOR
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagDefault, 2048);
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "../cli_i.h"
|
||||
#include "../cli_commands.h"
|
||||
#include "cli_shell_line.h"
|
||||
#include "cli_shell_completions.h"
|
||||
#include <stdio.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <m-array.h>
|
||||
@@ -11,21 +12,29 @@
|
||||
#include <toolbox/pipe.h>
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "CliShell"
|
||||
|
||||
#define ANSI_TIMEOUT_MS 10
|
||||
|
||||
typedef enum {
|
||||
CliShellComponentCompletions,
|
||||
CliShellComponentLine,
|
||||
CliShellComponentMAX, //<! do not use
|
||||
} CliShellComponent;
|
||||
|
||||
CliShellKeyComboSet* component_key_combo_sets[] = {
|
||||
[CliShellComponentCompletions] = &cli_shell_completions_key_combo_set,
|
||||
[CliShellComponentLine] = &cli_shell_line_key_combo_set,
|
||||
};
|
||||
static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets));
|
||||
|
||||
typedef enum {
|
||||
CliShellStorageEventMount,
|
||||
CliShellStorageEventUnmount,
|
||||
} CliShellStorageEvent;
|
||||
|
||||
struct CliShell {
|
||||
Cli* cli;
|
||||
FuriEventLoop* event_loop;
|
||||
@@ -34,6 +43,10 @@ struct CliShell {
|
||||
CliAnsiParser* ansi_parser;
|
||||
FuriEventLoopTimer* ansi_parsing_timer;
|
||||
|
||||
Storage* storage;
|
||||
FuriPubSubSubscription* storage_subscription;
|
||||
FuriMessageQueue* storage_event_queue;
|
||||
|
||||
void* components[CliShellComponentMAX];
|
||||
};
|
||||
|
||||
@@ -43,10 +56,73 @@ typedef struct {
|
||||
FuriString* args;
|
||||
} CliCommandThreadData;
|
||||
|
||||
static void cli_shell_data_available(PipeSide* pipe, void* context);
|
||||
static void cli_shell_pipe_broken(PipeSide* pipe, void* context);
|
||||
|
||||
static void cli_shell_install_pipe(CliShell* cli_shell) {
|
||||
pipe_install_as_stdio(cli_shell->pipe);
|
||||
pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop);
|
||||
pipe_set_callback_context(cli_shell->pipe, cli_shell);
|
||||
pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0);
|
||||
pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0);
|
||||
}
|
||||
|
||||
static void cli_shell_detach_pipe(CliShell* cli_shell) {
|
||||
pipe_detach_from_event_loop(cli_shell->pipe);
|
||||
furi_thread_set_stdin_callback(NULL, NULL);
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
}
|
||||
|
||||
// =========
|
||||
// Execution
|
||||
// =========
|
||||
|
||||
static int32_t cli_command_thread(void* context) {
|
||||
CliCommandThreadData* thread_data = context;
|
||||
if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio))
|
||||
pipe_install_as_stdio(thread_data->pipe);
|
||||
|
||||
thread_data->command->execute_callback(
|
||||
thread_data->pipe, thread_data->args, thread_data->command->context);
|
||||
|
||||
fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t cli_shell_string_distance(const char* s1, const char* s2) {
|
||||
size_t distance = 0;
|
||||
|
||||
while(*s1 && *s2) {
|
||||
if(*s1++ != *s2++) distance++;
|
||||
}
|
||||
while(*s1++)
|
||||
distance++;
|
||||
while(*s2++)
|
||||
distance++;
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
static void
|
||||
cli_shell_find_similar_command(CliShell* cli_shell, const char* input, FuriString* suggestion) {
|
||||
size_t min_distance = (size_t)-1;
|
||||
const size_t max_allowed = (strlen(input) + 1) / 2;
|
||||
furi_string_reset(suggestion);
|
||||
|
||||
cli_lock_commands(cli_shell->cli);
|
||||
CliCommandTree_t* commands = cli_get_commands(cli_shell->cli);
|
||||
for
|
||||
M_EACH(registered_command, *commands, CliCommandTree_t) {
|
||||
const char* command_name = furi_string_get_cstr(*registered_command->key_ptr);
|
||||
const size_t distance = cli_shell_string_distance(input, command_name);
|
||||
if(distance < min_distance && distance <= max_allowed) {
|
||||
min_distance = distance;
|
||||
furi_string_set(suggestion, command_name);
|
||||
}
|
||||
}
|
||||
cli_unlock_commands(cli_shell->cli);
|
||||
}
|
||||
|
||||
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
||||
// split command into command and args
|
||||
size_t space = furi_string_search_char(command, ' ');
|
||||
@@ -56,18 +132,59 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
||||
FuriString* args = furi_string_alloc_set(command);
|
||||
furi_string_right(args, space + 1);
|
||||
|
||||
PluginManager* plugin_manager = NULL;
|
||||
Loader* loader = NULL;
|
||||
CliCommand command_data;
|
||||
|
||||
do {
|
||||
// find handler
|
||||
if(!cli_get_command(cli_shell->cli, command_name, &command_data)) {
|
||||
printf(
|
||||
ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET,
|
||||
furi_string_get_cstr(command_name));
|
||||
FuriString* suggestion = furi_string_alloc();
|
||||
cli_shell_find_similar_command(
|
||||
cli_shell, furi_string_get_cstr(command_name), suggestion);
|
||||
if(furi_string_empty(suggestion)) {
|
||||
printf(
|
||||
ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET,
|
||||
furi_string_get_cstr(command_name));
|
||||
} else {
|
||||
printf(
|
||||
ANSI_FG_RED
|
||||
"could not find command `%s`, did you mean `%s`? Use `help` to list all available commands" ANSI_RESET,
|
||||
furi_string_get_cstr(command_name),
|
||||
furi_string_get_cstr(suggestion));
|
||||
}
|
||||
furi_string_free(suggestion);
|
||||
break;
|
||||
}
|
||||
|
||||
// load external command
|
||||
if(command_data.flags & CliCommandFlagExternal) {
|
||||
plugin_manager =
|
||||
plugin_manager_alloc(PLUGIN_APP_ID, 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));
|
||||
uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager);
|
||||
PluginManagerError error =
|
||||
plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path));
|
||||
furi_string_free(path);
|
||||
|
||||
if(error != PluginManagerErrorNone) {
|
||||
printf(ANSI_FG_RED "failed to load external command" ANSI_RESET);
|
||||
break;
|
||||
}
|
||||
|
||||
const CliCommandDescriptor* plugin =
|
||||
plugin_manager_get_ep(plugin_manager, plugin_cnt_last);
|
||||
furi_assert(plugin);
|
||||
furi_check(furi_string_cmp_str(command_name, plugin->name) == 0);
|
||||
command_data.execute_callback = plugin->execute_callback;
|
||||
command_data.flags = plugin->flags | CliCommandFlagExternal;
|
||||
command_data.stack_depth = plugin->stack_depth;
|
||||
|
||||
// external commands have to run in an external thread
|
||||
furi_check(!(command_data.flags & CliCommandFlagUseShellThread));
|
||||
}
|
||||
|
||||
// lock loader
|
||||
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
|
||||
loader = furi_record_open(RECORD_LOADER);
|
||||
@@ -79,7 +196,27 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
||||
}
|
||||
}
|
||||
|
||||
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
|
||||
if(command_data.flags & CliCommandFlagUseShellThread) {
|
||||
// run command in this thread
|
||||
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
|
||||
} else {
|
||||
// run command in separate thread
|
||||
cli_shell_detach_pipe(cli_shell);
|
||||
CliCommandThreadData thread_data = {
|
||||
.command = &command_data,
|
||||
.pipe = cli_shell->pipe,
|
||||
.args = args,
|
||||
};
|
||||
FuriThread* thread = furi_thread_alloc_ex(
|
||||
furi_string_get_cstr(command_name),
|
||||
command_data.stack_depth,
|
||||
cli_command_thread,
|
||||
&thread_data);
|
||||
furi_thread_start(thread);
|
||||
furi_thread_join(thread);
|
||||
furi_thread_free(thread);
|
||||
cli_shell_install_pipe(cli_shell);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
furi_string_free(command_name);
|
||||
@@ -88,13 +225,51 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
||||
// unlock loader
|
||||
if(loader) loader_unlock(loader);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
|
||||
// unload external command
|
||||
if(plugin_manager) plugin_manager_free(plugin_manager);
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Event handlers
|
||||
// ==============
|
||||
|
||||
static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) {
|
||||
static void cli_shell_storage_event(const void* message, void* context) {
|
||||
CliShell* cli_shell = context;
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
CliShellStorageEvent cli_event = CliShellStorageEventMount;
|
||||
furi_check(
|
||||
furi_message_queue_put(cli_shell->storage_event_queue, &cli_event, 0) == FuriStatusOk);
|
||||
} else if(event->type == StorageEventTypeCardUnmount) {
|
||||
CliShellStorageEvent cli_event = CliShellStorageEventUnmount;
|
||||
furi_check(
|
||||
furi_message_queue_put(cli_shell->storage_event_queue, &cli_event, 0) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void* context) {
|
||||
CliShell* cli_shell = context;
|
||||
FuriMessageQueue* queue = object;
|
||||
CliShellStorageEvent event;
|
||||
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
if(event == CliShellStorageEventMount) {
|
||||
cli_enumerate_external_commands(cli_shell->cli);
|
||||
} else if(event == CliShellStorageEventUnmount) {
|
||||
cli_remove_external_commands(cli_shell->cli);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cli_shell_process_parser_result(CliShell* cli_shell, CliAnsiParserResult parse_result) {
|
||||
if(!parse_result.is_done) return;
|
||||
CliKeyCombo key_combo = parse_result.result;
|
||||
if(key_combo.key == CliKeyUnrecognized) return;
|
||||
|
||||
for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008
|
||||
CliShellKeyComboSet* set = component_key_combo_sets[i];
|
||||
void* component_context = cli_shell->components[i];
|
||||
@@ -127,22 +302,13 @@ static void cli_shell_data_available(PipeSide* pipe, void* context) {
|
||||
// process ANSI escape sequences
|
||||
int c = getchar();
|
||||
furi_assert(c >= 0);
|
||||
CliAnsiParserResult parse_result = cli_ansi_parser_feed(cli_shell->ansi_parser, c);
|
||||
if(!parse_result.is_done) return;
|
||||
CliKeyCombo key_combo = parse_result.result;
|
||||
if(key_combo.key == CliKeyUnrecognized) return;
|
||||
|
||||
cli_shell_process_key(cli_shell, key_combo);
|
||||
cli_shell_process_parser_result(cli_shell, cli_ansi_parser_feed(cli_shell->ansi_parser, c));
|
||||
}
|
||||
|
||||
static void cli_shell_timer_expired(void* context) {
|
||||
CliShell* cli_shell = context;
|
||||
CliAnsiParserResult parse_result = cli_ansi_parser_feed_timeout(cli_shell->ansi_parser);
|
||||
if(!parse_result.is_done) return;
|
||||
CliKeyCombo key_combo = parse_result.result;
|
||||
if(key_combo.key == CliKeyUnrecognized) return;
|
||||
|
||||
cli_shell_process_key(cli_shell, key_combo);
|
||||
cli_shell_process_parser_result(
|
||||
cli_shell, cli_ansi_parser_feed_timeout(cli_shell->ansi_parser));
|
||||
}
|
||||
|
||||
// =======
|
||||
@@ -155,26 +321,42 @@ static CliShell* cli_shell_alloc(PipeSide* pipe) {
|
||||
cli_shell->cli = furi_record_open(RECORD_CLI);
|
||||
cli_shell->ansi_parser = cli_ansi_parser_alloc();
|
||||
cli_shell->pipe = pipe;
|
||||
pipe_install_as_stdio(cli_shell->pipe);
|
||||
|
||||
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]);
|
||||
|
||||
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);
|
||||
pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop);
|
||||
|
||||
pipe_set_callback_context(cli_shell->pipe, cli_shell);
|
||||
pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0);
|
||||
pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0);
|
||||
cli_shell_install_pipe(cli_shell);
|
||||
|
||||
cli_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,
|
||||
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);
|
||||
|
||||
return cli_shell;
|
||||
}
|
||||
|
||||
static void cli_shell_free(CliShell* cli_shell) {
|
||||
furi_pubsub_unsubscribe(
|
||||
storage_get_pubsub(cli_shell->storage), cli_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);
|
||||
|
||||
cli_shell_completions_free(cli_shell->components[CliShellComponentCompletions]);
|
||||
cli_shell_line_free(cli_shell->components[CliShellComponentLine]);
|
||||
|
||||
pipe_detach_from_event_loop(cli_shell->pipe);
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
#include "cli_shell_completions.h"
|
||||
|
||||
ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524
|
||||
#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions)
|
||||
|
||||
struct CliShellCompletions {
|
||||
Cli* cli;
|
||||
CliShell* shell;
|
||||
CliShellLine* line;
|
||||
CommandCompletions_t variants;
|
||||
size_t selected;
|
||||
bool is_displaying;
|
||||
};
|
||||
|
||||
#define COMPLETION_COLUMNS 3
|
||||
#define COMPLETION_COLUMN_WIDTH "30"
|
||||
#define COMPLETION_COLUMN_WIDTH_I 30
|
||||
|
||||
/**
|
||||
* @brief Update for the completions menu
|
||||
*/
|
||||
typedef enum {
|
||||
CliShellCompletionsActionOpen,
|
||||
CliShellCompletionsActionClose,
|
||||
CliShellCompletionsActionUp,
|
||||
CliShellCompletionsActionDown,
|
||||
CliShellCompletionsActionLeft,
|
||||
CliShellCompletionsActionRight,
|
||||
CliShellCompletionsActionSelect,
|
||||
CliShellCompletionsActionSelectNoClose,
|
||||
} CliShellCompletionsAction;
|
||||
|
||||
typedef enum {
|
||||
CliShellCompletionSegmentTypeCommand,
|
||||
CliShellCompletionSegmentTypeArguments,
|
||||
} CliShellCompletionSegmentType;
|
||||
|
||||
typedef struct {
|
||||
CliShellCompletionSegmentType type;
|
||||
size_t start;
|
||||
size_t length;
|
||||
} CliShellCompletionSegment;
|
||||
|
||||
// ==========
|
||||
// Public API
|
||||
// ==========
|
||||
|
||||
CliShellCompletions* cli_shell_completions_alloc(Cli* cli, CliShell* shell, CliShellLine* line) {
|
||||
CliShellCompletions* completions = malloc(sizeof(CliShellCompletions));
|
||||
|
||||
completions->cli = cli;
|
||||
completions->shell = shell;
|
||||
completions->line = line;
|
||||
CommandCompletions_init(completions->variants);
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
void cli_shell_completions_free(CliShellCompletions* completions) {
|
||||
CommandCompletions_clear(completions->variants);
|
||||
free(completions);
|
||||
}
|
||||
|
||||
// =======
|
||||
// Helpers
|
||||
// =======
|
||||
|
||||
CliShellCompletionSegment cli_shell_completions_segment(CliShellCompletions* completions) {
|
||||
furi_assert(completions);
|
||||
CliShellCompletionSegment segment;
|
||||
|
||||
FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line));
|
||||
furi_string_left(input, cli_shell_line_get_line_position(completions->line));
|
||||
|
||||
// find index of first non-space character
|
||||
size_t first_non_space = 0;
|
||||
while(1) {
|
||||
size_t ret = furi_string_search_char(input, ' ', first_non_space);
|
||||
if(ret == FURI_STRING_FAILURE) break;
|
||||
if(ret - first_non_space > 1) break;
|
||||
first_non_space++;
|
||||
}
|
||||
|
||||
size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space);
|
||||
|
||||
if(first_space_in_command == FURI_STRING_FAILURE) {
|
||||
segment.type = CliShellCompletionSegmentTypeCommand;
|
||||
segment.start = first_non_space;
|
||||
segment.length = furi_string_size(input) - first_non_space;
|
||||
} else {
|
||||
segment.type = CliShellCompletionSegmentTypeArguments;
|
||||
segment.start = 0;
|
||||
segment.length = 0;
|
||||
// support removed, might reimplement in the future
|
||||
}
|
||||
|
||||
furi_string_free(input);
|
||||
return segment;
|
||||
}
|
||||
|
||||
void cli_shell_completions_fill_variants(CliShellCompletions* completions) {
|
||||
furi_assert(completions);
|
||||
CommandCompletions_reset(completions->variants);
|
||||
|
||||
CliShellCompletionSegment segment = cli_shell_completions_segment(completions);
|
||||
FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line));
|
||||
furi_string_right(input, segment.start);
|
||||
furi_string_left(input, segment.length);
|
||||
|
||||
if(segment.type == CliShellCompletionSegmentTypeCommand) {
|
||||
cli_lock_commands(completions->cli);
|
||||
CliCommandTree_t* commands = cli_get_commands(completions->cli);
|
||||
for
|
||||
M_EACH(registered_command, *commands, CliCommandTree_t) {
|
||||
FuriString* command_name = *registered_command->key_ptr;
|
||||
if(furi_string_start_with(command_name, input)) {
|
||||
CommandCompletions_push_back(completions->variants, command_name);
|
||||
}
|
||||
}
|
||||
cli_unlock_commands(completions->cli);
|
||||
|
||||
} else {
|
||||
// support removed, might reimplement in the future
|
||||
}
|
||||
|
||||
furi_string_free(input);
|
||||
}
|
||||
|
||||
static size_t cli_shell_completions_rows_at_column(CliShellCompletions* completions, size_t x) {
|
||||
size_t completions_size = CommandCompletions_size(completions->variants);
|
||||
size_t n_full_rows = completions_size / COMPLETION_COLUMNS;
|
||||
size_t n_cols_in_last_row = completions_size % COMPLETION_COLUMNS;
|
||||
size_t n_rows_at_x = n_full_rows + ((x >= n_cols_in_last_row) ? 0 : 1);
|
||||
return n_rows_at_x;
|
||||
}
|
||||
|
||||
void cli_shell_completions_render(
|
||||
CliShellCompletions* completions,
|
||||
CliShellCompletionsAction action) {
|
||||
furi_assert(completions);
|
||||
if(action == CliShellCompletionsActionOpen) furi_check(!completions->is_displaying);
|
||||
if(action == CliShellCompletionsActionClose) furi_check(completions->is_displaying);
|
||||
|
||||
char prompt[64];
|
||||
cli_shell_line_format_prompt(completions->line, prompt, sizeof(prompt));
|
||||
|
||||
if(action == CliShellCompletionsActionOpen) {
|
||||
cli_shell_completions_fill_variants(completions);
|
||||
completions->selected = 0;
|
||||
|
||||
if(CommandCompletions_size(completions->variants) == 1) {
|
||||
cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose);
|
||||
return;
|
||||
}
|
||||
|
||||
// show completions menu (full re-render)
|
||||
printf("\n\r");
|
||||
size_t position = 0;
|
||||
for
|
||||
M_EACH(completion, completions->variants, CommandCompletions_t) {
|
||||
if(position == completions->selected) printf(ANSI_INVERT);
|
||||
printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion));
|
||||
if(position == completions->selected) printf(ANSI_RESET);
|
||||
if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) &&
|
||||
position != CommandCompletions_size(completions->variants)) {
|
||||
printf("\r\n");
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
if(!position) {
|
||||
printf(ANSI_FG_RED "no completions" ANSI_RESET);
|
||||
}
|
||||
|
||||
size_t total_rows = (position / COMPLETION_COLUMNS) + 1;
|
||||
printf(
|
||||
ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu")
|
||||
ANSI_CURSOR_HOR_POS("%zu"),
|
||||
total_rows,
|
||||
strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1);
|
||||
|
||||
completions->is_displaying = true;
|
||||
|
||||
} else if(action == CliShellCompletionsActionClose) {
|
||||
// clear completions menu
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END)
|
||||
ANSI_CURSOR_HOR_POS("%zu"),
|
||||
strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) + 1,
|
||||
strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1);
|
||||
completions->is_displaying = false;
|
||||
|
||||
} else if(
|
||||
action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown ||
|
||||
action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) {
|
||||
if(CommandCompletions_empty_p(completions->variants)) return;
|
||||
|
||||
// move selection
|
||||
size_t completions_size = CommandCompletions_size(completions->variants);
|
||||
size_t old_selection = completions->selected;
|
||||
int n_columns = (completions_size >= COMPLETION_COLUMNS) ? COMPLETION_COLUMNS :
|
||||
completions_size;
|
||||
int selection_unclamped = old_selection;
|
||||
if(action == CliShellCompletionsActionLeft) {
|
||||
selection_unclamped--;
|
||||
} else if(action == CliShellCompletionsActionRight) {
|
||||
selection_unclamped++;
|
||||
} else {
|
||||
int selection_x = old_selection % COMPLETION_COLUMNS;
|
||||
int selection_y_unclamped = old_selection / COMPLETION_COLUMNS;
|
||||
if(action == CliShellCompletionsActionUp) selection_y_unclamped--;
|
||||
if(action == CliShellCompletionsActionDown) selection_y_unclamped++;
|
||||
size_t selection_y = 0;
|
||||
if(selection_y_unclamped < 0) {
|
||||
selection_x = CLAMP_WRAPAROUND(selection_x - 1, n_columns - 1, 0);
|
||||
selection_y =
|
||||
cli_shell_completions_rows_at_column(completions, selection_x) - 1; // -V537
|
||||
} else if(
|
||||
(size_t)selection_y_unclamped >
|
||||
cli_shell_completions_rows_at_column(completions, selection_x) - 1) {
|
||||
selection_x = CLAMP_WRAPAROUND(selection_x + 1, n_columns - 1, 0);
|
||||
selection_y = 0;
|
||||
} else {
|
||||
selection_y = selection_y_unclamped;
|
||||
}
|
||||
selection_unclamped = (selection_y * COMPLETION_COLUMNS) + selection_x;
|
||||
}
|
||||
size_t new_selection = CLAMP_WRAPAROUND(selection_unclamped, (int)completions_size - 1, 0);
|
||||
completions->selected = new_selection;
|
||||
|
||||
if(new_selection != old_selection) {
|
||||
// determine selection coordinates relative to top-left of suggestion menu
|
||||
size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I;
|
||||
size_t old_y = old_selection / COMPLETION_COLUMNS;
|
||||
size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I;
|
||||
size_t new_y = new_selection / COMPLETION_COLUMNS;
|
||||
printf("\n\r");
|
||||
|
||||
// print old selection in normal colors
|
||||
if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y);
|
||||
printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1);
|
||||
printf(
|
||||
"%-" COMPLETION_COLUMN_WIDTH "s",
|
||||
furi_string_get_cstr(
|
||||
*CommandCompletions_cget(completions->variants, old_selection)));
|
||||
if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y);
|
||||
printf(ANSI_CURSOR_HOR_POS("1"));
|
||||
|
||||
// print new selection in inverted colors
|
||||
if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y);
|
||||
printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1);
|
||||
printf(
|
||||
ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET,
|
||||
furi_string_get_cstr(
|
||||
*CommandCompletions_cget(completions->variants, new_selection)));
|
||||
|
||||
// return cursor
|
||||
printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1);
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu"),
|
||||
strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) +
|
||||
1);
|
||||
}
|
||||
|
||||
} else if(action == CliShellCompletionsActionSelectNoClose) {
|
||||
// insert selection into prompt
|
||||
CliShellCompletionSegment segment = cli_shell_completions_segment(completions);
|
||||
FuriString* input = cli_shell_line_get_selected(completions->line);
|
||||
FuriString* completion =
|
||||
*CommandCompletions_cget(completions->variants, completions->selected);
|
||||
furi_string_replace_at(
|
||||
input, segment.start, segment.length, furi_string_get_cstr(completion));
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
|
||||
strlen(prompt) + 1,
|
||||
furi_string_get_cstr(input));
|
||||
|
||||
int position_change = (int)furi_string_size(completion) - (int)segment.length;
|
||||
cli_shell_line_set_line_position(
|
||||
completions->line,
|
||||
MAX(0, (int)cli_shell_line_get_line_position(completions->line) + position_change));
|
||||
|
||||
} else if(action == CliShellCompletionsActionSelect) {
|
||||
cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose);
|
||||
cli_shell_completions_render(completions, CliShellCompletionsActionClose);
|
||||
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Input handlers
|
||||
// ==============
|
||||
|
||||
static bool hide_if_open_and_continue_handling(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellCompletions* completions = context;
|
||||
if(completions->is_displaying)
|
||||
cli_shell_completions_render(completions, CliShellCompletionsActionClose);
|
||||
return false; // process other home events
|
||||
}
|
||||
|
||||
static bool key_combo_cr(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellCompletions* completions = context;
|
||||
if(!completions->is_displaying) return false;
|
||||
cli_shell_completions_render(completions, CliShellCompletionsActionSelect);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool key_combo_up_down(CliKeyCombo combo, void* context) {
|
||||
CliShellCompletions* completions = context;
|
||||
if(!completions->is_displaying) return false;
|
||||
cli_shell_completions_render(
|
||||
completions,
|
||||
(combo.key == CliKeyUp) ? CliShellCompletionsActionUp : CliShellCompletionsActionDown);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool key_combo_left_right(CliKeyCombo combo, void* context) {
|
||||
CliShellCompletions* completions = context;
|
||||
if(!completions->is_displaying) return false;
|
||||
cli_shell_completions_render(
|
||||
completions,
|
||||
(combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft :
|
||||
CliShellCompletionsActionRight);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool key_combo_tab(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellCompletions* completions = context;
|
||||
cli_shell_completions_render(
|
||||
completions,
|
||||
completions->is_displaying ? CliShellCompletionsActionRight :
|
||||
CliShellCompletionsActionOpen);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool key_combo_esc(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellCompletions* completions = context;
|
||||
if(!completions->is_displaying) return false;
|
||||
cli_shell_completions_render(completions, CliShellCompletionsActionClose);
|
||||
return true;
|
||||
}
|
||||
|
||||
CliShellKeyComboSet cli_shell_completions_key_combo_set = {
|
||||
.fallback = hide_if_open_and_continue_handling,
|
||||
.count = 7,
|
||||
.records =
|
||||
{
|
||||
{{CliModKeyNo, CliKeyCR}, key_combo_cr},
|
||||
{{CliModKeyNo, CliKeyUp}, key_combo_up_down},
|
||||
{{CliModKeyNo, CliKeyDown}, key_combo_up_down},
|
||||
{{CliModKeyNo, CliKeyLeft}, key_combo_left_right},
|
||||
{{CliModKeyNo, CliKeyRight}, key_combo_left_right},
|
||||
{{CliModKeyNo, CliKeyTab}, key_combo_tab},
|
||||
{{CliModKeyNo, CliKeyEsc}, key_combo_esc},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <m-array.h>
|
||||
#include "cli_shell_i.h"
|
||||
#include "cli_shell_line.h"
|
||||
#include "../cli.h"
|
||||
#include "../cli_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct CliShellCompletions CliShellCompletions;
|
||||
|
||||
CliShellCompletions* cli_shell_completions_alloc(Cli* cli, CliShell* shell, CliShellLine* line);
|
||||
|
||||
void cli_shell_completions_free(CliShellCompletions* completions);
|
||||
|
||||
extern CliShellKeyComboSet cli_shell_completions_key_combo_set;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -65,6 +65,64 @@ void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t cli_shell_line_get_line_position(CliShellLine* line) {
|
||||
return line->line_position;
|
||||
}
|
||||
|
||||
void cli_shell_line_set_line_position(CliShellLine* line, size_t position) {
|
||||
line->line_position = position;
|
||||
}
|
||||
|
||||
// =======
|
||||
// Helpers
|
||||
// =======
|
||||
|
||||
typedef enum {
|
||||
CliCharClassWord,
|
||||
CliCharClassSpace,
|
||||
CliCharClassOther,
|
||||
} CliCharClass;
|
||||
|
||||
typedef enum {
|
||||
CliSkipDirectionLeft,
|
||||
CliSkipDirectionRight,
|
||||
} CliSkipDirection;
|
||||
|
||||
CliCharClass cli_shell_line_char_class(char c) {
|
||||
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
|
||||
return CliCharClassWord;
|
||||
} else if(c == ' ') {
|
||||
return CliCharClassSpace;
|
||||
} else {
|
||||
return CliCharClassOther;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
cli_shell_line_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) {
|
||||
if(furi_string_size(string) == 0) return original_pos;
|
||||
if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos;
|
||||
if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string))
|
||||
return original_pos;
|
||||
|
||||
int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0;
|
||||
int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1;
|
||||
int32_t position = original_pos;
|
||||
CliCharClass start_class =
|
||||
cli_shell_line_char_class(furi_string_get_char(string, position + look_offset));
|
||||
|
||||
while(true) {
|
||||
position += increment;
|
||||
if(position < 0) break;
|
||||
if(position >= (int32_t)furi_string_size(string)) break;
|
||||
if(cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)) !=
|
||||
start_class)
|
||||
break;
|
||||
}
|
||||
|
||||
return MAX(0, position);
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Input handlers
|
||||
// ==============
|
||||
@@ -87,13 +145,32 @@ static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
|
||||
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
furi_string_trim(command);
|
||||
if(furi_string_empty(command)) {
|
||||
cli_shell_line_prompt(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* command_copy = furi_string_alloc_set(command);
|
||||
|
||||
if(line->history_position == 0) {
|
||||
for(size_t i = 1; i < line->history_entries; i++) {
|
||||
if(furi_string_cmp(line->history[i], command) == 0) {
|
||||
line->history_position = i;
|
||||
command = cli_shell_line_get_selected(line);
|
||||
furi_string_trim(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move selected command to the front
|
||||
if(line->history_position > 0) {
|
||||
// move selected command to the front
|
||||
size_t pos = line->history_position;
|
||||
size_t len = line->history_entries;
|
||||
memmove(
|
||||
&line->history[1], &line->history[0], line->history_position * sizeof(FuriString*));
|
||||
line->history[0] = command;
|
||||
&line->history[pos], &line->history[pos + 1], (len - pos - 1) * sizeof(FuriString*));
|
||||
furi_string_move(line->history[0], command);
|
||||
line->history_entries--;
|
||||
}
|
||||
|
||||
// insert empty command
|
||||
@@ -109,7 +186,7 @@ static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
|
||||
|
||||
// execute command
|
||||
printf("\r\n");
|
||||
if(!furi_string_empty(command_copy)) cli_shell_execute_command(line->shell, command_copy);
|
||||
cli_shell_execute_command(line->shell, command_copy);
|
||||
furi_string_free(command_copy);
|
||||
|
||||
cli_shell_line_prompt(line);
|
||||
@@ -199,7 +276,57 @@ static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_fallback(CliKeyCombo combo, void* context) {
|
||||
static bool cli_shell_line_input_ctrl_l(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// clear screen
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
char prompt[64];
|
||||
cli_shell_line_format_prompt(line, prompt, sizeof(prompt));
|
||||
printf(
|
||||
ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS(
|
||||
"1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"),
|
||||
prompt,
|
||||
furi_string_get_cstr(command),
|
||||
strlen(prompt) + line->line_position + 1 /* 1-based column indexing */);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_ctrl_left_right(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
// skip run of similar chars to the left or right
|
||||
FuriString* selected_line = cli_shell_line_get_selected(line);
|
||||
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft :
|
||||
CliSkipDirectionRight;
|
||||
line->line_position = cli_shell_line_skip_run(selected_line, line->line_position, direction);
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_ctrl_bksp(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// delete run of similar chars to the left
|
||||
cli_shell_line_ensure_not_overwriting_history(line);
|
||||
FuriString* selected_line = cli_shell_line_get_selected(line);
|
||||
size_t run_start =
|
||||
cli_shell_line_skip_run(selected_line, line->line_position, CliSkipDirectionLeft);
|
||||
furi_string_replace_at(selected_line, run_start, line->line_position - run_start, "");
|
||||
line->line_position = run_start;
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END)
|
||||
ANSI_CURSOR_HOR_POS("%zu"),
|
||||
cli_shell_line_prompt_length(line) + line->line_position + 1,
|
||||
furi_string_get_cstr(selected_line) + run_start,
|
||||
cli_shell_line_prompt_length(line) + run_start + 1);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_normal(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
if(combo.modifiers != CliModKeyNo) return false;
|
||||
if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false;
|
||||
@@ -220,8 +347,8 @@ static bool cli_shell_line_input_fallback(CliKeyCombo combo, void* context) {
|
||||
}
|
||||
|
||||
CliShellKeyComboSet cli_shell_line_key_combo_set = {
|
||||
.fallback = cli_shell_line_input_fallback,
|
||||
.count = 10,
|
||||
.fallback = cli_shell_line_input_normal,
|
||||
.count = 14,
|
||||
.records =
|
||||
{
|
||||
{{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c},
|
||||
@@ -234,5 +361,9 @@ CliShellKeyComboSet cli_shell_line_key_combo_set = {
|
||||
{{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end},
|
||||
{{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp},
|
||||
{{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp},
|
||||
{{CliModKeyNo, CliKeyFF}, cli_shell_line_input_ctrl_l},
|
||||
{{CliModKeyCtrl, CliKeyLeft}, cli_shell_line_input_ctrl_left_right},
|
||||
{{CliModKeyCtrl, CliKeyRight}, cli_shell_line_input_ctrl_left_right},
|
||||
{{CliModKeyNo, CliKeyETB}, cli_shell_line_input_ctrl_bksp},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,6 +24,10 @@ void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length);
|
||||
|
||||
void cli_shell_line_prompt(CliShellLine* line);
|
||||
|
||||
size_t cli_shell_line_get_line_position(CliShellLine* line);
|
||||
|
||||
void cli_shell_line_set_line_position(CliShellLine* line, size_t position);
|
||||
|
||||
/**
|
||||
* @brief If a line from history has been selected, moves it into the active line
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
App(
|
||||
appid="crypto_cli",
|
||||
appid="cli_crypto",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="crypto_cli_plugin_ep",
|
||||
entry_point="cli_crypto_ep",
|
||||
requires=["cli"],
|
||||
sources=["crypto_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="crypto_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="crypto_on_system_start",
|
||||
sources=["crypto_cli.c"],
|
||||
order=10,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
|
||||
void crypto_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
@@ -280,7 +281,7 @@ void crypto_cli_store_key(PipeSide* pipe, FuriString* args) {
|
||||
furi_string_free(key_type);
|
||||
}
|
||||
|
||||
static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -317,15 +318,4 @@ static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("crypto", crypto_cli)
|
||||
|
||||
void crypto_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(crypto_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(crypto, execute, CliCommandFlagDefault, 1024);
|
||||
|
||||
@@ -11,10 +11,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="input_cli",
|
||||
appid="cli_input",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="input_cli_plugin_ep",
|
||||
entry_point="cli_input_ep",
|
||||
requires=["cli"],
|
||||
sources=["input_cli.c"],
|
||||
)
|
||||
|
||||
@@ -30,9 +30,6 @@ typedef struct {
|
||||
volatile uint32_t counter;
|
||||
} InputPinState;
|
||||
|
||||
/** Input CLI command handler */
|
||||
void input_cli_wrapper(PipeSide* pipe, FuriString* args, void* context);
|
||||
|
||||
// #define INPUT_DEBUG
|
||||
|
||||
#define GPIO_Read(input_pin) (furi_hal_gpio_read(input_pin.pin->gpio) ^ (input_pin.pin->inverted))
|
||||
@@ -104,11 +101,6 @@ int32_t input_srv(void* p) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
|
||||
#endif
|
||||
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli_wrapper, event_pubsub);
|
||||
#endif
|
||||
|
||||
InputPinState pin_states[input_pins_count];
|
||||
|
||||
for(size_t i = 0; i < input_pins_count; i++) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
@@ -195,9 +196,9 @@ static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_p
|
||||
furi_string_free(key_str);
|
||||
}
|
||||
|
||||
void input_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_assert(context);
|
||||
FuriPubSub* event_pubsub = context;
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriPubSub* event_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
|
||||
@@ -223,7 +224,7 @@ void input_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
} while(false);
|
||||
|
||||
furi_string_free(cmd);
|
||||
furi_record_close(RECORD_INPUT_EVENTS);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("input", input_cli)
|
||||
CLI_COMMAND_INTERFACE(input, execute, CliCommandFlagParallelSafe, 1024);
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="loader_srv",
|
||||
cdefines=["SRV_LOADER"],
|
||||
requires=["gui"],
|
||||
provides=["loader_start"],
|
||||
stack_size=2 * 1024,
|
||||
order=90,
|
||||
sdk_headers=[
|
||||
@@ -15,18 +14,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="loader_cli",
|
||||
appid="cli_loader",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="loader_cli_plugin_ep",
|
||||
entry_point="cli_loader_ep",
|
||||
requires=["cli"],
|
||||
sources=["loader_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="loader_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="loader_on_system_start",
|
||||
requires=["loader"],
|
||||
order=90,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <applications.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
@@ -114,7 +115,7 @@ static void loader_cli_signal(FuriString* args, Loader* loader) {
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
@@ -142,15 +143,4 @@ static void loader_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_LOADER);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("loader", loader_cli)
|
||||
|
||||
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_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(loader_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(loader, execute, CliCommandFlagParallelSafe, 1024);
|
||||
|
||||
@@ -10,7 +10,6 @@ App(
|
||||
],
|
||||
provides=[
|
||||
"power_settings",
|
||||
"power_start",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=110,
|
||||
@@ -18,18 +17,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="power_cli",
|
||||
appid="cli_power",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="power_cli_plugin_ep",
|
||||
entry_point="cli_power_ep",
|
||||
requires=["cli"],
|
||||
sources=["power_cli.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="power_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="power_on_system_start",
|
||||
requires=["power"],
|
||||
order=80,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <toolbox/pipe.h>
|
||||
@@ -68,7 +69,7 @@ static void power_cli_command_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void power_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -112,17 +113,4 @@ void power_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("power", power_cli)
|
||||
|
||||
void power_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
|
||||
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli_wrapper, NULL);
|
||||
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(power_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(power, execute, CliCommandFlagParallelSafe, 1024);
|
||||
|
||||
@@ -321,9 +321,11 @@ static void storage_cli_write_chunk(PipeSide* pipe, FuriString* path, FuriString
|
||||
uint8_t* buffer = malloc(buffer_size);
|
||||
|
||||
while(need_to_read) {
|
||||
size_t read_this_time = pipe_receive(pipe, buffer, MIN(buffer_size, need_to_read));
|
||||
size_t wrote_this_time = storage_file_write(file, buffer, read_this_time);
|
||||
size_t to_read_this_time = MIN(buffer_size, need_to_read);
|
||||
size_t read_this_time = pipe_receive(pipe, buffer, to_read_this_time);
|
||||
if(read_this_time != to_read_this_time) break;
|
||||
|
||||
size_t wrote_this_time = storage_file_write(file, buffer, read_this_time);
|
||||
if(wrote_this_time != read_this_time) {
|
||||
storage_cli_print_error(storage_file_get_error(file));
|
||||
break;
|
||||
@@ -720,7 +722,13 @@ 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, "storage", CliCommandFlagParallelSafe, storage_cli, NULL, 512);
|
||||
cli_add_command_ex(
|
||||
cli,
|
||||
"storage",
|
||||
CliCommandFlagParallelSafe | CliCommandFlagUseShellThread,
|
||||
storage_cli,
|
||||
NULL,
|
||||
512);
|
||||
cli_add_command(
|
||||
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
|
||||
@@ -10,7 +10,6 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
resources="examples",
|
||||
order=0,
|
||||
provides=["js_app_start"],
|
||||
sources=[
|
||||
"js_app.c",
|
||||
"js_modules.c",
|
||||
@@ -24,10 +23,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_cli",
|
||||
appid="cli_js",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_cli_execute_plugin_ep",
|
||||
entry_point="cli_js_ep",
|
||||
requires=["cli"],
|
||||
sources=[
|
||||
"js_app.c",
|
||||
@@ -40,14 +39,6 @@ App(
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_app_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="js_app_on_system_start",
|
||||
order=160,
|
||||
sources=["js_app.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_event_loop",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <toolbox/path.h>
|
||||
#include <assets_icons.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#define TAG "JS app"
|
||||
@@ -207,13 +208,4 @@ void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("js", js_cli_execute)
|
||||
|
||||
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_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(js, js_cli_execute, CliCommandFlagDefault, 1024);
|
||||
|
||||
@@ -57,19 +57,17 @@ Pause script execution by a defined time.
|
||||
|
||||
### Modifier keys
|
||||
|
||||
Can be combined with a special key command or a single character.
|
||||
| Command | Notes |
|
||||
| -------------- | ---------- |
|
||||
| CONTROL / CTRL | |
|
||||
| SHIFT | |
|
||||
| ALT | |
|
||||
| WINDOWS / GUI | |
|
||||
| CTRL-ALT | CTRL+ALT |
|
||||
| CTRL-SHIFT | CTRL+SHIFT |
|
||||
| ALT-SHIFT | ALT+SHIFT |
|
||||
| ALT-GUI | ALT+WIN |
|
||||
| GUI-SHIFT | WIN+SHIFT |
|
||||
| GUI-CTRL | WIN+CTRL |
|
||||
The following modifier keys are recognized:
|
||||
| Command | Notes |
|
||||
| ------- | ------------ |
|
||||
| CTRL | |
|
||||
| CONTROL | Same as CTRL |
|
||||
| SHIFT | |
|
||||
| ALT | |
|
||||
| GUI | |
|
||||
| WINDOWS | Same as GUI |
|
||||
|
||||
You can chain multiple modifier keys together using hyphens (`-`) or spaces.
|
||||
|
||||
## Key hold and release
|
||||
|
||||
|
||||
@@ -295,10 +295,12 @@ void memmgr_heap_printf_free_blocks(void) {
|
||||
//can be enabled once we can do printf with a locked scheduler
|
||||
//vTaskSuspendAll();
|
||||
|
||||
pxBlock = xStart.pxNextFreeBlock;
|
||||
while(pxBlock->pxNextFreeBlock != NULL) {
|
||||
pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock);
|
||||
heapVALIDATE_BLOCK_POINTER(pxBlock);
|
||||
while(pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER(NULL)) {
|
||||
printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize);
|
||||
pxBlock = pxBlock->pxNextFreeBlock;
|
||||
pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock);
|
||||
heapVALIDATE_BLOCK_POINTER(pxBlock);
|
||||
}
|
||||
|
||||
//xTaskResumeAll();
|
||||
|
||||
@@ -109,6 +109,8 @@ class FlipperStorage:
|
||||
|
||||
def start(self):
|
||||
self.port.open()
|
||||
time.sleep(0.5)
|
||||
self.read.until(self.CLI_PROMPT)
|
||||
self.port.reset_input_buffer()
|
||||
# Send a command with a known syntax to make sure the buffer is flushed
|
||||
self.send("device_info\r")
|
||||
|
||||
@@ -786,8 +786,10 @@ 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_vcp_disable,void,CliVcp*
|
||||
Function,+,cli_vcp_enable,void,CliVcp*
|
||||
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
||||
|
||||
|
@@ -883,8 +883,10 @@ 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_vcp_disable,void,CliVcp*
|
||||
Function,+,cli_vcp_enable,void,CliVcp*
|
||||
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
||||
@@ -3572,10 +3574,10 @@ Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, con
|
||||
Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker*
|
||||
Function,+,subghz_keystore_alloc,SubGhzKeystore*,
|
||||
Function,+,subghz_keystore_free,void,SubGhzKeystore*
|
||||
Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore*
|
||||
Function,+,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore*
|
||||
Function,+,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*"
|
||||
Function,+,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*"
|
||||
Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t"
|
||||
Function,+,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t"
|
||||
Function,-,subghz_keystore_reset_kl,void,SubGhzKeystore*
|
||||
Function,+,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*"
|
||||
Function,+,subghz_protocol_alutech_at_4n_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*"
|
||||
|
||||
|
Reference in New Issue
Block a user