| // 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; |
| } |