Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FL-3909] CLI improvements, part I #3928

Merged
merged 10 commits into from
Oct 14, 2024
9 changes: 4 additions & 5 deletions applications/main/subghz/subghz_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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');
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions applications/main/subghz/subghz_cli.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cli/cli.h>
#include <cli/cli_ansi.h>

void subghz_on_system_start(void);
180 changes: 132 additions & 48 deletions applications/services/cli/cli.c
Original file line number Diff line number Diff line change
@@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -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"
Expand All @@ -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) {
Expand All @@ -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);
}

Expand All @@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) {

cli->cursor_position--;
} else {
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -305,8 +308,72 @@ static void cli_handle_autocomplete(Cli* cli) {
cli_prompt(cli);
}

static void cli_handle_escape(Cli* cli, char c) {
if(c == 'A') {
/**
* @brief Determines the class that a character belongs to
*
* The return value of this function does not make sense on its own; it's only
* useful for comparing it with other values returned by this function. This
* function is used internally in `cli_skip_run`
*/
static uint8_t cli_char_class(char c) {
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
return 0;
} else if(c == ' ') {
return 1;
} else {
return 255;
portasynthinca3 marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* @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:
* left (`-1`) or right (`1`)
* @returns The position at which the run ends
*/
static size_t cli_skip_run(FuriString* string, size_t original_pos, int8_t direction) {
portasynthinca3 marked this conversation as resolved.
Show resolved Hide resolved
if(furi_string_size(string) == 0) return original_pos;
if(direction == -1 && original_pos == 0) return original_pos;
if(direction == 1 && original_pos == furi_string_size(string)) return original_pos;

int8_t look_offset = direction == -1 ? -1 : 0;
int32_t position = original_pos;
uint8_t start_class = cli_char_class(furi_string_get_char(string, position + look_offset));

while(true) {
position += direction;
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
Expand All @@ -315,67 +382,84 @@ 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 \(-_-)/

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

} else if(
combo.modifiers == CliModKeyCtrl &&
(combo.key == CliKeyLeft || combo.key == CliKeyRight)) {
// Skip run of similar chars to the left or right
int32_t direction = (combo.key == CliKeyLeft) ? -1 : 1;
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(in_chr == CliSymbolAsciiCR) {

} 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, -1);
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(
(in_chr >= 0x20 && in_chr < 0x7F) && //-V560
(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(
Expand Down
16 changes: 1 addition & 15 deletions applications/services/cli/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading