| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * (C) Copyright 2000 | |
| * Wolfgang Denk, DENX Software Engineering, wd@denx.de. | |
| * | |
| * Copyright 2022 Google LLC | |
| */ | |
| #include <cli.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <linux/errno.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; | |
| else | |
| act = ESC_CONVERTED; | |
| 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 ichar; | |
| } | |
| 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->emitting) { | |
| if (cch->emit_upto < cch->esc_len) | |
| return cch->esc_save[cch->emit_upto++]; | |
| cch->emit_upto = 0; | |
| cch->emitting = false; | |
| cch->esc_len = 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; | |
| ichar = 0; | |
| break; | |
| case ESC_REJECT: | |
| /* | |
| * invalid escape sequence, start returning the | |
| * characters in it | |
| */ | |
| cch->esc_save[cch->esc_len++] = ichar; | |
| ichar = cch->esc_save[cch->emit_upto++]; | |
| cch->emitting = true; | |
| return ichar; | |
| case ESC_CONVERTED: | |
| /* valid escape sequence, return the resulting char */ | |
| cch->esc_len = 0; | |
| break; | |
| } | |
| } | |
| 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; | |
| } |