Merge branch 'dev' into nestednonces

This commit is contained in:
Nathan N 2024-10-14 12:08:36 -04:00 committed by GitHub
commit f530eeed47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 530 additions and 103 deletions

View File

@ -999,13 +999,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
switch(chat_event.event) {
case SubGhzChatEventInputData:
if(chat_event.c == CliSymbolAsciiETX) {
if(chat_event.c == CliKeyETX) {
printf("\r\n");
chat_event.event = SubGhzChatEventUserExit;
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
break;
} else if(
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
} else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) {
size_t len = furi_string_utf8_length(input);
if(len > furi_string_utf8_length(name)) {
printf("%s", "\e[D\e[1P");
@ -1027,7 +1026,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
}
furi_string_set(input, sysmsg);
}
} else if(chat_event.c == CliSymbolAsciiCR) {
} else if(chat_event.c == CliKeyCR) {
printf("\r\n");
furi_string_push_back(input, '\r');
furi_string_push_back(input, '\n');
@ -1041,7 +1040,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
furi_string_printf(input, "%s", furi_string_get_cstr(name));
printf("%s", furi_string_get_cstr(input));
fflush(stdout);
} else if(chat_event.c == CliSymbolAsciiLF) {
} else if(chat_event.c == CliKeyLF) {
//cut out the symbol \n
} else {
putc(chat_event.c, stdout);

View File

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

View File

@ -1,12 +1,15 @@
#include "cli_i.h"
#include "cli_commands.h"
#include "cli_vcp.h"
#include "cli_ansi.h"
#include <furi_hal_version.h>
#include <loader/loader.h>
#define TAG "CliSrv"
#define CLI_INPUT_LEN_LIMIT 256
#define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :(
#define CLI_PROMPT_LENGTH 3 // printable characters
Cli* cli_alloc(void) {
Cli* cli = malloc(sizeof(Cli));
@ -85,7 +88,7 @@ bool cli_cmd_interrupt_received(Cli* cli) {
char c = '\0';
if(cli_is_connected(cli)) {
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
return c == CliSymbolAsciiETX;
return c == CliKeyETX;
}
} else {
return true;
@ -102,7 +105,8 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
}
void cli_motd(void) {
printf("\r\n"
printf(ANSI_FLIPPER_BRAND_ORANGE
"\r\n"
" _.-------.._ -,\r\n"
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
@ -116,12 +120,11 @@ void cli_motd(void) {
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
"\r\n"
"Welcome to Flipper Zero Command Line Interface!\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET
"\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE
"Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n"
"Read the manual: https://docs.flipper.net/development/cli\r\n"
"Run `help` or `?` to list available commands\r\n"
"\r\n");
"Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n");
const Version* firmware_version = furi_hal_version_get_firmware_version();
if(firmware_version) {
@ -142,7 +145,7 @@ void cli_nl(Cli* cli) {
void cli_prompt(Cli* cli) {
UNUSED(cli);
printf("\r\n>: %s", furi_string_get_cstr(cli->line));
printf("\r\n" CLI_PROMPT "%s", furi_string_get_cstr(cli->line));
fflush(stdout);
}
@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) {
cli->cursor_position--;
} else {
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
}
@ -241,7 +244,7 @@ static void cli_handle_enter(Cli* cli) {
printf(
"`%s` command not found, use `help` or `?` to list all available commands",
furi_string_get_cstr(command));
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
cli_reset(cli);
@ -305,8 +308,85 @@ static void cli_handle_autocomplete(Cli* cli) {
cli_prompt(cli);
}
static void cli_handle_escape(Cli* cli, char c) {
if(c == 'A') {
typedef enum {
CliCharClassWord,
CliCharClassSpace,
CliCharClassOther,
} CliCharClass;
/**
* @brief Determines the class that a character belongs to
*
* The return value of this function should not be used on its own; it should
* only be used for comparing it with other values returned by this function.
* This function is used internally in `cli_skip_run`.
*/
static CliCharClass cli_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;
}
}
typedef enum {
CliSkipDirectionLeft,
CliSkipDirectionRight,
} CliSkipDirection;
/**
* @brief Skips a run of a class of characters
*
* @param string Input string
* @param original_pos Position to start the search at
* @param direction Direction in which to perform the search
* @returns The position at which the run ends
*/
static size_t cli_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_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_char_class(furi_string_get_char(string, position + look_offset)) != start_class)
break;
}
return MAX(0, position);
}
void cli_process_input(Cli* cli) {
CliKeyCombo combo = cli_read_ansi_key_combo(cli);
FURI_LOG_T(TAG, "code=0x%02x, mod=0x%x\r\n", combo.key, combo.modifiers);
if(combo.key == CliKeyTab) {
cli_handle_autocomplete(cli);
} else if(combo.key == CliKeySOH) {
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
cli_prompt(cli);
} else if(combo.key == CliKeyETX) {
cli_reset(cli);
cli_prompt(cli);
} else if(combo.key == CliKeyEOT) {
cli_reset(cli);
} else if(combo.key == CliKeyUp && combo.modifiers == CliModKeyNo) {
// Use previous command if line buffer is empty
if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) {
// Set line buffer and cursor position
@ -315,67 +395,85 @@ static void cli_handle_escape(Cli* cli, char c) {
// Show new line to user
printf("%s", furi_string_get_cstr(cli->line));
}
} else if(c == 'B') {
} else if(c == 'C') {
} else if(combo.key == CliKeyDown && combo.modifiers == CliModKeyNo) {
// Clear input buffer
furi_string_reset(cli->line);
cli->cursor_position = 0;
printf("\r" CLI_PROMPT "\e[0K");
} else if(combo.key == CliKeyRight && combo.modifiers == CliModKeyNo) {
// Move right
if(cli->cursor_position < furi_string_size(cli->line)) {
cli->cursor_position++;
printf("\e[C");
}
} else if(c == 'D') {
} else if(combo.key == CliKeyLeft && combo.modifiers == CliModKeyNo) {
// Move left
if(cli->cursor_position > 0) {
cli->cursor_position--;
printf("\e[D");
}
}
fflush(stdout);
}
void cli_process_input(Cli* cli) {
char in_chr = cli_getc(cli);
size_t rx_len;
} else if(combo.key == CliKeyHome && combo.modifiers == CliModKeyNo) {
// Move to beginning of line
cli->cursor_position = 0;
printf("\e[%uG", CLI_PROMPT_LENGTH + 1); // columns start at 1 \(-_-)/
} else if(combo.key == CliKeyEnd && combo.modifiers == CliModKeyNo) {
// Move to end of line
cli->cursor_position = furi_string_size(cli->line);
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1);
if(in_chr == CliSymbolAsciiTab) {
cli_handle_autocomplete(cli);
} else if(in_chr == CliSymbolAsciiSOH) {
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
cli_prompt(cli);
} else if(in_chr == CliSymbolAsciiETX) {
cli_reset(cli);
cli_prompt(cli);
} else if(in_chr == CliSymbolAsciiEOT) {
cli_reset(cli);
} else if(in_chr == CliSymbolAsciiEsc) {
rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
if((rx_len > 0) && (in_chr == '[')) {
cli_read(cli, (uint8_t*)&in_chr, 1);
cli_handle_escape(cli, in_chr);
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
cli_handle_backspace(cli);
} else if(in_chr == CliSymbolAsciiCR) {
cli_handle_enter(cli);
} else if(
(in_chr >= 0x20 && in_chr < 0x7F) && //-V560
combo.modifiers == CliModKeyCtrl &&
(combo.key == CliKeyLeft || combo.key == CliKeyRight)) {
// Skip run of similar chars to the left or right
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft :
CliSkipDirectionRight;
cli->cursor_position = cli_skip_run(cli->line, cli->cursor_position, direction);
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1);
} else if(combo.key == CliKeyBackspace || combo.key == CliKeyDEL) {
cli_handle_backspace(cli);
} else if(combo.key == CliKeyETB) { // Ctrl + Backspace
// Delete run of similar chars to the left
size_t run_start = cli_skip_run(cli->line, cli->cursor_position, CliSkipDirectionLeft);
furi_string_replace_at(cli->line, run_start, cli->cursor_position - run_start, "");
cli->cursor_position = run_start;
printf(
"\e[%zuG%s\e[0K\e[%zuG", // move cursor, print second half of line, erase remains, move cursor again
CLI_PROMPT_LENGTH + cli->cursor_position + 1,
furi_string_get_cstr(cli->line) + run_start,
CLI_PROMPT_LENGTH + run_start + 1);
} else if(combo.key == CliKeyCR) {
cli_handle_enter(cli);
} else if(
(combo.key >= 0x20 && combo.key < 0x7F) && //-V560
(furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) {
if(cli->cursor_position == furi_string_size(cli->line)) {
furi_string_push_back(cli->line, in_chr);
cli_putc(cli, in_chr);
furi_string_push_back(cli->line, combo.key);
cli_putc(cli, combo.key);
} else {
// Insert character to line buffer
const char in_str[2] = {in_chr, 0};
const char in_str[2] = {combo.key, 0};
furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str);
// Print character in replace mode
printf("\e[4h%c\e[4l", in_chr);
printf("\e[4h%c\e[4l", combo.key);
fflush(stdout);
}
cli->cursor_position++;
} else {
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
fflush(stdout);
}
void cli_add_command(

View File

@ -10,26 +10,12 @@
extern "C" {
#endif
typedef enum {
CliSymbolAsciiSOH = 0x01,
CliSymbolAsciiETX = 0x03,
CliSymbolAsciiEOT = 0x04,
CliSymbolAsciiBell = 0x07,
CliSymbolAsciiBackspace = 0x08,
CliSymbolAsciiTab = 0x09,
CliSymbolAsciiLF = 0x0A,
CliSymbolAsciiCR = 0x0D,
CliSymbolAsciiEsc = 0x1B,
CliSymbolAsciiUS = 0x1F,
CliSymbolAsciiSpace = 0x20,
CliSymbolAsciiDel = 0x7F,
} CliSymbols;
typedef enum {
CliCommandFlagDefault = 0, /**< Default, loader lock is used */
CliCommandFlagParallelSafe =
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
CliCommandFlagHidden = (1 << 2), /**< Not shown in `help` */
} CliCommandFlag;
#define RECORD_CLI "cli"

View File

@ -0,0 +1,76 @@
#include "cli_ansi.h"
/**
* @brief Converts a single character representing a special key into the enum
* representation
*/
static CliKey cli_ansi_key_from_mnemonic(char c) {
switch(c) {
case 'A':
return CliKeyUp;
case 'B':
return CliKeyDown;
case 'C':
return CliKeyRight;
case 'D':
return CliKeyLeft;
case 'F':
return CliKeyEnd;
case 'H':
return CliKeyHome;
default:
return CliKeyUnrecognized;
}
}
CliKeyCombo cli_read_ansi_key_combo(Cli* cli) {
char ch = cli_getc(cli);
if(ch != CliKeyEsc)
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = ch,
};
ch = cli_getc(cli);
// ESC ESC -> ESC
if(ch == '\e')
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = '\e',
};
// ESC <char> -> Alt + <char>
if(ch != '[')
return (CliKeyCombo){
.modifiers = CliModKeyAlt,
.key = cli_getc(cli),
};
ch = cli_getc(cli);
// ESC [ 1
if(ch == '1') {
// ESC [ 1 ; <modifier bitfield> <key mnemonic>
if(cli_getc(cli) == ';') {
CliModKey modifiers = (cli_getc(cli) - '0'); // convert following digit to a number
modifiers &= ~1;
return (CliKeyCombo){
.modifiers = modifiers,
.key = cli_ansi_key_from_mnemonic(cli_getc(cli)),
};
}
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = CliKeyUnrecognized,
};
}
// ESC [ <key mnemonic>
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = cli_ansi_key_from_mnemonic(ch),
};
}

View File

@ -0,0 +1,94 @@
#pragma once
#include "cli.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ANSI_RESET "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_FG_BLACK "\e[30m"
#define ANSI_FG_RED "\e[31m"
#define ANSI_FG_GREEN "\e[32m"
#define ANSI_FG_YELLOW "\e[33m"
#define ANSI_FG_BLUE "\e[34m"
#define ANSI_FG_MAGENTA "\e[35m"
#define ANSI_FG_CYAN "\e[36m"
#define ANSI_FG_WHITE "\e[37m"
#define ANSI_FG_BR_BLACK "\e[90m"
#define ANSI_FG_BR_RED "\e[91m"
#define ANSI_FG_BR_GREEN "\e[92m"
#define ANSI_FG_BR_YELLOW "\e[93m"
#define ANSI_FG_BR_BLUE "\e[94m"
#define ANSI_FG_BR_MAGENTA "\e[95m"
#define ANSI_FG_BR_CYAN "\e[96m"
#define ANSI_FG_BR_WHITE "\e[97m"
#define ANSI_BG_BLACK "\e[40m"
#define ANSI_BG_RED "\e[41m"
#define ANSI_BG_GREEN "\e[42m"
#define ANSI_BG_YELLOW "\e[43m"
#define ANSI_BG_BLUE "\e[44m"
#define ANSI_BG_MAGENTA "\e[45m"
#define ANSI_BG_CYAN "\e[46m"
#define ANSI_BG_WHITE "\e[47m"
#define ANSI_BG_BR_BLACK "\e[100m"
#define ANSI_BG_BR_RED "\e[101m"
#define ANSI_BG_BR_GREEN "\e[102m"
#define ANSI_BG_BR_YELLOW "\e[103m"
#define ANSI_BG_BR_BLUE "\e[104m"
#define ANSI_BG_BR_MAGENTA "\e[105m"
#define ANSI_BG_BR_CYAN "\e[106m"
#define ANSI_BG_BR_WHITE "\e[107m"
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
typedef enum {
CliKeyUnrecognized = 0,
CliKeySOH = 0x01,
CliKeyETX = 0x03,
CliKeyEOT = 0x04,
CliKeyBell = 0x07,
CliKeyBackspace = 0x08,
CliKeyTab = 0x09,
CliKeyLF = 0x0A,
CliKeyCR = 0x0D,
CliKeyETB = 0x17,
CliKeyEsc = 0x1B,
CliKeyUS = 0x1F,
CliKeySpace = 0x20,
CliKeyDEL = 0x7F,
CliKeySpecial = 0x80,
CliKeyLeft,
CliKeyRight,
CliKeyUp,
CliKeyDown,
CliKeyHome,
CliKeyEnd,
} CliKey;
typedef enum {
CliModKeyNo = 0,
CliModKeyAlt = 2,
CliModKeyCtrl = 4,
CliModKeyMeta = 8,
} CliModKey;
typedef struct {
CliModKey modifiers;
CliKey key;
} CliKeyCombo;
/**
* @brief Reads a key or key combination
*/
CliKeyCombo cli_read_ansi_key_combo(Cli* cli);
#ifdef __cplusplus
}
#endif

View File

@ -1,5 +1,6 @@
#include "cli_commands.h"
#include "cli_command_gpio.h"
#include "cli_ansi.h"
#include <core/thread.h>
#include <furi_hal.h>
@ -10,6 +11,7 @@
#include <loader/loader.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.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"
@ -52,37 +54,196 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_help(Cli* cli, FuriString* args, void* context) {
// Lil Easter egg :>
void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
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(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
printf("Commands available:");
// Command count
const size_t commands_count = CliCommandTree_size(cli->commands);
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
// Count non-hidden commands
CliCommandTree_it_t it_count;
CliCommandTree_it(it_count, cli->commands);
size_t commands_count = 0;
while(!CliCommandTree_end_p(it_count)) {
if(!(CliCommandTree_cref(it_count)->value_ptr->flags & CliCommandFlagHidden))
commands_count++;
CliCommandTree_next(it_count);
}
// Use 2 iterators from start and middle to show 2 columns
CliCommandTree_it_t it_left;
CliCommandTree_it(it_left, cli->commands);
CliCommandTree_it_t it_right;
CliCommandTree_it(it_right, cli->commands);
for(size_t i = 0; i < commands_count_mid; i++)
CliCommandTree_next(it_right);
// Create iterators starting at different positions
const size_t columns = 3;
const size_t commands_per_column = (commands_count / columns) + (commands_count % columns);
CliCommandTree_it_t iterators[columns];
for(size_t c = 0; c < columns; c++) {
CliCommandTree_it(iterators[c], cli->commands);
for(size_t i = 0; i < c * commands_per_column; i++)
CliCommandTree_next(iterators[c]);
}
// Iterate throw tree
for(size_t i = 0; i < commands_count_mid; i++) {
// Print commands
for(size_t r = 0; r < commands_per_column; r++) {
printf("\r\n");
// Left Column
if(!CliCommandTree_end_p(it_left)) {
printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
CliCommandTree_next(it_left);
for(size_t c = 0; c < columns; c++) {
if(!CliCommandTree_end_p(iterators[c])) {
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]);
if(!(item->value_ptr->flags & CliCommandFlagHidden)) {
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
}
CliCommandTree_next(iterators[c]);
}
}
// Right Column
if(!CliCommandTree_end_p(it_right)) {
printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
CliCommandTree_next(it_right);
}
};
}
if(furi_string_size(args) > 0) {
cli_nl(cli);
@ -391,16 +552,18 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
int interval = 1000;
args_read_int_and_trim(args, &interval);
if(interval) printf("\e[2J\e[?25l"); // Clear display, hide cursor
FuriThreadList* thread_list = furi_thread_list_alloc();
while(!cli_cmd_interrupt_received(cli)) {
uint32_t tick = furi_get_tick();
furi_thread_enumerate(thread_list);
if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0
if(interval) printf("\e[0;0f"); // Return to 0,0
uint32_t uptime = tick / furi_kernel_get_tick_frequency();
printf(
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n",
"\rThreads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\e[0K\r\n",
furi_thread_list_size(thread_list),
(double)furi_thread_list_get_isr_time(thread_list),
uptime / 60 / 60,
@ -408,14 +571,14 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
uptime % 60);
printf(
"Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
"\rHeap: total %zu, free %zu, minimum %zu, max block %zu\e[0K\r\n\r\n",
memmgr_get_total_heap(),
memmgr_get_free_heap(),
memmgr_get_minimum_free_heap(),
memmgr_heap_get_max_free_block());
printf(
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
"\r%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\e[0K\r\n",
"AppID",
"Name",
"State",
@ -429,7 +592,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
printf(
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
"\r%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\e[0K\r\n",
item->app_id,
item->name,
item->state,
@ -448,6 +611,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
}
}
furi_thread_list_free(thread_list);
if(interval) printf("\e[?25h"); // Show cursor
}
void cli_command_free(Cli* cli, FuriString* args, void* context) {
@ -499,6 +664,12 @@ 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,
"neofetch",
CliCommandFlagParallelSafe | CliCommandFlagHidden,
cli_command_neofetch,
NULL);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);

View File

@ -3,6 +3,7 @@
#include <lib/toolbox/args.h>
#include <cli/cli.h>
#include <cli/cli_ansi.h>
void crypto_cli_print_usage(void) {
printf("Usage:\r\n");
@ -45,14 +46,14 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
input = furi_string_alloc();
char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) {
if(c == CliKeyETX) {
printf("\r\n");
break;
} else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout);
fflush(stdout);
furi_string_push_back(input, c);
} else if(c == CliSymbolAsciiCR) {
} else if(c == CliKeyCR) {
printf("\r\n");
furi_string_cat(input, "\r\n");
}
@ -120,14 +121,14 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
hex_input = furi_string_alloc();
char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) {
if(c == CliKeyETX) {
printf("\r\n");
break;
} else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout);
fflush(stdout);
furi_string_push_back(hex_input, c);
} else if(c == CliSymbolAsciiCR) {
} else if(c == CliKeyCR) {
printf("\r\n");
}
}

View File

@ -2,6 +2,7 @@
#include <furi_hal.h>
#include <cli/cli.h>
#include <cli/cli_ansi.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h>
@ -224,7 +225,7 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
while(true) {
uint8_t symbol = cli_getc(cli);
if(symbol == CliSymbolAsciiETX) {
if(symbol == CliKeyETX) {
size_t write_size = read_index % buffer_size;
if(write_size > 0) {