diff options
Diffstat (limited to 'common/cli_getch.c')
-rw-r--r-- | common/cli_getch.c | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/common/cli_getch.c b/common/cli_getch.c new file mode 100644 index 0000000000..9eeea7fef2 --- /dev/null +++ b/common/cli_getch.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * Copyright 2022 Google LLC + */ + +#include <common.h> +#include <cli.h> + +/** + * enum cli_esc_state_t - indicates what to do with an escape character + * + * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are + * returned from each subsequent call to cli_ch_esc() + * @ESC_SAVE: Character should be saved in esc_save until we have another one + * @ESC_CONVERTED: Escape sequence has been completed and the resulting + * character is available + */ +enum cli_esc_state_t { + ESC_REJECT, + ESC_SAVE, + ESC_CONVERTED +}; + +void cli_ch_init(struct cli_ch_state *cch) +{ + memset(cch, '\0', sizeof(*cch)); +} + +/** + * cli_ch_esc() - Process a character in an ongoing escape sequence + * + * @cch: State information + * @ichar: Character to process + * @actp: Returns the action to take + * Returns: Output character if *actp is ESC_CONVERTED, else 0 + */ +static int cli_ch_esc(struct cli_ch_state *cch, int ichar, + enum cli_esc_state_t *actp) +{ + enum cli_esc_state_t act = ESC_REJECT; + + switch (cch->esc_len) { + case 1: + if (ichar == '[' || ichar == 'O') + act = ESC_SAVE; + break; + case 2: + switch (ichar) { + case 'D': /* <- key */ + ichar = CTL_CH('b'); + act = ESC_CONVERTED; + break; /* pass off to ^B handler */ + case 'C': /* -> key */ + ichar = CTL_CH('f'); + act = ESC_CONVERTED; + break; /* pass off to ^F handler */ + case 'H': /* Home key */ + ichar = CTL_CH('a'); + act = ESC_CONVERTED; + break; /* pass off to ^A handler */ + case 'F': /* End key */ + ichar = CTL_CH('e'); + act = ESC_CONVERTED; + break; /* pass off to ^E handler */ + case 'A': /* up arrow */ + ichar = CTL_CH('p'); + act = ESC_CONVERTED; + break; /* pass off to ^P handler */ + case 'B': /* down arrow */ + ichar = CTL_CH('n'); + act = ESC_CONVERTED; + break; /* pass off to ^N handler */ + case '1': + case '2': + case '3': + case '4': + case '7': + case '8': + if (cch->esc_save[1] == '[') { + /* see if next character is ~ */ + act = ESC_SAVE; + } + break; + } + break; + case 3: + switch (ichar) { + case '~': + switch (cch->esc_save[2]) { + case '3': /* Delete key */ + ichar = CTL_CH('d'); + act = ESC_CONVERTED; + break; /* pass to ^D handler */ + case '1': /* Home key */ + case '7': + ichar = CTL_CH('a'); + act = ESC_CONVERTED; + break; /* pass to ^A handler */ + case '4': /* End key */ + case '8': + ichar = CTL_CH('e'); + act = ESC_CONVERTED; + break; /* pass to ^E handler */ + } + break; + case '0': + if (cch->esc_save[2] == '2') + act = ESC_SAVE; + break; + } + break; + case 4: + switch (ichar) { + case '0': + case '1': + act = ESC_SAVE; + break; /* bracketed paste */ + } + break; + case 5: + if (ichar == '~') { /* bracketed paste */ + ichar = 0; + act = ESC_CONVERTED; + } + } + + *actp = act; + + return act == ESC_CONVERTED ? ichar : 0; +} + +int cli_ch_process(struct cli_ch_state *cch, int ichar) +{ + /* + * ichar=0x0 when error occurs in U-Boot getchar() or when the caller + * wants to check if there are more characters saved in the escape + * sequence + */ + if (!ichar) { + if (cch->emit_upto) { + if (cch->emit_upto < cch->esc_len) + return cch->esc_save[cch->emit_upto++]; + cch->emit_upto = 0; + } + return 0; + } else if (ichar == -ETIMEDOUT) { + /* + * If we are in an escape sequence but nothing has followed the + * Escape character, then the user probably just pressed the + * Escape key. Return it and clear the sequence. + */ + if (cch->esc_len) { + cch->esc_len = 0; + return '\e'; + } + + /* Otherwise there is nothing to return */ + return 0; + } + + if (ichar == '\n' || ichar == '\r') + return '\n'; + + /* handle standard linux xterm esc sequences for arrow key, etc. */ + if (cch->esc_len != 0) { + enum cli_esc_state_t act; + + ichar = cli_ch_esc(cch, ichar, &act); + + switch (act) { + case ESC_SAVE: + /* save this character and return nothing */ + cch->esc_save[cch->esc_len++] = ichar; + return 0; + case ESC_REJECT: + /* + * invalid escape sequence, start returning the + * characters in it + */ + cch->esc_save[cch->esc_len++] = ichar; + return cch->esc_save[cch->emit_upto++]; + case ESC_CONVERTED: + /* valid escape sequence, return the resulting char */ + cch->esc_len = 0; + return ichar; + } + } + + if (ichar == '\e') { + if (!cch->esc_len) { + cch->esc_save[cch->esc_len] = ichar; + cch->esc_len = 1; + } else { + puts("impossible condition #876\n"); + cch->esc_len = 0; + } + return 0; + } + + return ichar; +} |