| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 |
- // SPDX-License-Identifier: Apache-2.0
- /*
- * Copyright (C) 2013 Intel Corporation
- *
- */
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
- #include <stdbool.h>
- #include <termios.h>
- #include <stdlib.h>
- #include "terminal.h"
- #include "history.h"
- /*
- * Character sequences recognized by code in this file
- * Leading ESC 0x1B is not included
- */
- #define SEQ_INSERT "[2~"
- #define SEQ_DELETE "[3~"
- #define SEQ_HOME "OH"
- #define SEQ_END "OF"
- #define SEQ_PGUP "[5~"
- #define SEQ_PGDOWN "[6~"
- #define SEQ_LEFT "[D"
- #define SEQ_RIGHT "[C"
- #define SEQ_UP "[A"
- #define SEQ_DOWN "[B"
- #define SEQ_STAB "[Z"
- #define SEQ_M_n "n"
- #define SEQ_M_p "p"
- #define SEQ_CLEFT "[1;5D"
- #define SEQ_CRIGHT "[1;5C"
- #define SEQ_CUP "[1;5A"
- #define SEQ_CDOWN "[1;5B"
- #define SEQ_SLEFT "[1;2D"
- #define SEQ_SRIGHT "[1;2C"
- #define SEQ_SUP "[1;2A"
- #define SEQ_SDOWN "[1;2B"
- #define SEQ_MLEFT "[1;3D"
- #define SEQ_MRIGHT "[1;3C"
- #define SEQ_MUP "[1;3A"
- #define SEQ_MDOWN "[1;3B"
- #define KEY_SEQUENCE(k) { KEY_##k, SEQ_##k }
- struct ansii_sequence {
- int code;
- const char *sequence;
- };
- /* Table connects single int key codes with character sequences */
- static const struct ansii_sequence ansii_sequnces[] = {
- KEY_SEQUENCE(INSERT),
- KEY_SEQUENCE(DELETE),
- KEY_SEQUENCE(HOME),
- KEY_SEQUENCE(END),
- KEY_SEQUENCE(PGUP),
- KEY_SEQUENCE(PGDOWN),
- KEY_SEQUENCE(LEFT),
- KEY_SEQUENCE(RIGHT),
- KEY_SEQUENCE(UP),
- KEY_SEQUENCE(DOWN),
- KEY_SEQUENCE(CLEFT),
- KEY_SEQUENCE(CRIGHT),
- KEY_SEQUENCE(CUP),
- KEY_SEQUENCE(CDOWN),
- KEY_SEQUENCE(SLEFT),
- KEY_SEQUENCE(SRIGHT),
- KEY_SEQUENCE(SUP),
- KEY_SEQUENCE(SDOWN),
- KEY_SEQUENCE(MLEFT),
- KEY_SEQUENCE(MRIGHT),
- KEY_SEQUENCE(MUP),
- KEY_SEQUENCE(MDOWN),
- KEY_SEQUENCE(STAB),
- KEY_SEQUENCE(M_p),
- KEY_SEQUENCE(M_n),
- { 0, NULL }
- };
- #define KEY_SEQUNCE_NOT_FINISHED -1
- #define KEY_C_C 3
- #define KEY_C_D 4
- #define KEY_C_L 12
- #define isseqence(c) ((c) == 0x1B)
- /*
- * Number of characters that consist of ANSI sequence
- * Should not be less then longest string in ansi_sequences
- */
- #define MAX_ASCII_SEQUENCE 10
- static char current_sequence[MAX_ASCII_SEQUENCE];
- static int current_sequence_len = -1;
- /* single line typed by user goes here */
- static char line_buf[LINE_BUF_MAX];
- /* index of cursor in input line */
- static int line_buf_ix = 0;
- /* current length of input line */
- static int line_len = 0;
- /* line index used for fetching lines from history */
- static int line_index = 0;
- static char prompt_buf[10] = "> ";
- static const char *const noprompt = "";
- static const char *current_prompt = prompt_buf;
- static const char *prompt = prompt_buf;
- /*
- * Moves cursor to right or left
- *
- * n - positive - moves cursor right
- * n - negative - moves cursor left
- */
- static void terminal_move_cursor(int n)
- {
- if (n < 0) {
- for (; n < 0; n++)
- putchar('\b');
- } else if (n > 0) {
- printf("%*s", n, line_buf + line_buf_ix);
- }
- }
- /* Draw command line */
- void terminal_draw_command_line(void)
- {
- /*
- * this needs to be checked here since line_buf is not cleared
- * before parsing event though line_len and line_buf_ix are
- */
- if (line_len > 0)
- printf("%s%s", prompt, line_buf);
- else
- printf("%s", prompt);
- /* move cursor to it's place */
- terminal_move_cursor(line_buf_ix - line_len);
- }
- /* inserts string into command line at cursor position */
- void terminal_insert_into_command_line(const char *p)
- {
- int len = strlen(p);
- if (line_len == line_buf_ix) {
- strcat(line_buf, p);
- printf("%s", p);
- line_len = line_len + len;
- line_buf_ix = line_len;
- } else {
- memmove(line_buf + line_buf_ix + len,
- line_buf + line_buf_ix, line_len - line_buf_ix + 1);
- memmove(line_buf + line_buf_ix, p, len);
- printf("%s", line_buf + line_buf_ix);
- line_buf_ix += len;
- line_len += len;
- terminal_move_cursor(line_buf_ix - line_len);
- }
- }
- /* Prints string and redraws command line */
- int terminal_print(const char *format, ...)
- {
- va_list args;
- int ret;
- va_start(args, format);
- ret = terminal_vprint(format, args);
- va_end(args);
- return ret;
- }
- /* Prints string and redraws command line */
- int terminal_vprint(const char *format, va_list args)
- {
- int ret;
- printf("\r%*s\r", (int) line_len + 1, " ");
- ret = vprintf(format, args);
- terminal_draw_command_line();
- fflush(stdout);
- return ret;
- }
- /*
- * Call this when text in line_buf was changed
- * and line needs to be redrawn
- */
- static void terminal_line_replaced(void)
- {
- int len = strlen(line_buf);
- /* line is shorter that previous */
- if (len < line_len) {
- /* if new line is shorter move cursor to end of new end */
- while (line_buf_ix > len) {
- putchar('\b');
- line_buf_ix--;
- }
- /* If cursor was not at the end, move it to the end */
- if (line_buf_ix < line_len)
- printf("%.*s", line_len - line_buf_ix,
- line_buf + line_buf_ix);
- /* over write end of previous line */
- while (line_len >= len++)
- putchar(' ');
- }
- /* draw new line */
- printf("\r%s%s", prompt, line_buf);
- /* set up indexes to new line */
- line_len = strlen(line_buf);
- line_buf_ix = line_len;
- fflush(stdout);
- }
- static void terminal_clear_line(void)
- {
- line_buf[0] = '\0';
- terminal_line_replaced();
- }
- static void terminal_clear_screen(void)
- {
- line_buf[0] = '\0';
- line_buf_ix = 0;
- line_len = 0;
- printf("\x1b[2J\x1b[1;1H%s", prompt);
- }
- static void terminal_delete_char(void)
- {
- /* delete character under cursor if not at the very end */
- if (line_buf_ix >= line_len)
- return;
- /*
- * Prepare buffer with one character missing
- * trailing 0 is moved
- */
- line_len--;
- memmove(line_buf + line_buf_ix, line_buf + line_buf_ix + 1,
- line_len - line_buf_ix + 1);
- /* print rest of line from current cursor position */
- printf("%s \b", line_buf + line_buf_ix);
- /* move back cursor */
- terminal_move_cursor(line_buf_ix - line_len);
- }
- /*
- * Function tries to replace current line with specified line in history
- * new_line_index - new line to show, -1 to show oldest
- */
- static void terminal_get_line_from_history(int new_line_index)
- {
- new_line_index = history_get_line(new_line_index,
- line_buf, LINE_BUF_MAX);
- if (new_line_index >= 0) {
- terminal_line_replaced();
- line_index = new_line_index;
- }
- }
- /*
- * Function searches history back or forward for command line that starts
- * with characters up to cursor position
- *
- * back - true - searches backward
- * back - false - searches forward (more recent commands)
- */
- static void terminal_match_hitory(bool back)
- {
- char buf[line_buf_ix + 1];
- int line;
- int matching_line = -1;
- int dir = back ? 1 : -1;
- line = line_index + dir;
- while (matching_line == -1 && line >= 0) {
- int new_line_index;
- new_line_index = history_get_line(line, buf, line_buf_ix + 1);
- if (new_line_index < 0)
- break;
- if (0 == strncmp(line_buf, buf, line_buf_ix))
- matching_line = line;
- line += dir;
- }
- if (matching_line >= 0) {
- int pos = line_buf_ix;
- terminal_get_line_from_history(matching_line);
- /* move back to cursor position to original place */
- line_buf_ix = pos;
- terminal_move_cursor(pos - line_len);
- }
- }
- /*
- * Converts terminal character sequences to single value representing
- * keyboard keys
- */
- static int terminal_convert_sequence(int c)
- {
- int i;
- /* Not in sequence yet? */
- if (current_sequence_len == -1) {
- /* Is ansi sequence detected by 0x1B ? */
- if (isseqence(c)) {
- current_sequence_len++;
- return KEY_SEQUNCE_NOT_FINISHED;
- }
- return c;
- }
- /* Inside sequence */
- current_sequence[current_sequence_len++] = c;
- current_sequence[current_sequence_len] = '\0';
- for (i = 0; ansii_sequnces[i].code; ++i) {
- /* Matches so far? */
- if (0 != strncmp(current_sequence, ansii_sequnces[i].sequence,
- current_sequence_len))
- continue;
- /* Matches as a whole? */
- if (ansii_sequnces[i].sequence[current_sequence_len] == 0) {
- current_sequence_len = -1;
- return ansii_sequnces[i].code;
- }
- /* partial match (not whole sequence yet) */
- return KEY_SEQUNCE_NOT_FINISHED;
- }
- terminal_print("ansi char 0x%X %c\n", c);
- /*
- * Sequence does not match
- * mark that no in sequence any more, return char
- */
- current_sequence_len = -1;
- return c;
- }
- typedef void (*terminal_action)(int c, line_callback process_line);
- #define TERMINAL_ACTION(n) \
- static void n(int c, void (*process_line)(char *line))
- TERMINAL_ACTION(terminal_action_null)
- {
- }
- /* Mapping between keys and function */
- typedef struct {
- int key;
- terminal_action func;
- } KeyAction;
- int action_keys[] = {
- KEY_SEQUNCE_NOT_FINISHED,
- KEY_LEFT,
- KEY_RIGHT,
- KEY_HOME,
- KEY_END,
- KEY_DELETE,
- KEY_CLEFT,
- KEY_CRIGHT,
- KEY_SUP,
- KEY_SDOWN,
- KEY_UP,
- KEY_DOWN,
- KEY_BACKSPACE,
- KEY_INSERT,
- KEY_PGUP,
- KEY_PGDOWN,
- KEY_CUP,
- KEY_CDOWN,
- KEY_SLEFT,
- KEY_SRIGHT,
- KEY_MLEFT,
- KEY_MRIGHT,
- KEY_MUP,
- KEY_MDOWN,
- KEY_STAB,
- KEY_M_n,
- KEY_M_p,
- KEY_C_C,
- KEY_C_D,
- KEY_C_L,
- '\t',
- '\r',
- '\n',
- };
- #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
- /*
- * current_actions holds all recognizable kes and actions for them
- * additional element (index 0) is used for default action
- */
- static KeyAction current_actions[NELEM(action_keys) + 1];
- /* KeyAction comparator by key, for qsort and bsearch */
- static int KeyActionKeyCompare(const void *a, const void *b)
- {
- return ((const KeyAction *) a)->key - ((const KeyAction *) b)->key;
- }
- /* Find action by key, NULL if no action for this key */
- static KeyAction *terminal_get_action(int key)
- {
- KeyAction a = { .key = key };
- return bsearch(&a, current_actions + 1, NELEM(action_keys), sizeof(a),
- KeyActionKeyCompare);
- }
- /* Sets new set of actions to use */
- static void terminal_set_actions(const KeyAction *actions)
- {
- int i;
- /* Make map with empty function for every key */
- for (i = 0; i < NELEM(action_keys); ++i) {
- /*
- * + 1 due to 0 index reserved for default action that is
- * called for non mapped key
- */
- current_actions[i + 1].key = action_keys[i];
- current_actions[i + 1].func = terminal_action_null;
- }
- /* Sort action from 1 (index 0 - default action) */
- qsort(current_actions + 1, NELEM(action_keys), sizeof(KeyAction),
- KeyActionKeyCompare);
- /* Set default action (first in array) */
- current_actions[0] = *actions++;
- /* Copy rest of actions into their places */
- for (; actions->key; ++actions) {
- KeyAction *place = terminal_get_action(actions->key);
- if (place)
- place->func = actions->func;
- }
- }
- TERMINAL_ACTION(terminal_action_left)
- {
- /* if not at the beginning move to previous character */
- if (line_buf_ix <= 0)
- return;
- line_buf_ix--;
- terminal_move_cursor(-1);
- }
- TERMINAL_ACTION(terminal_action_right)
- {
- /*
- * If not at the end, just print current character
- * and modify position
- */
- if (line_buf_ix < line_len)
- putchar(line_buf[line_buf_ix++]);
- }
- TERMINAL_ACTION(terminal_action_home)
- {
- /* move to beginning of line and update position */
- printf("\r%s", prompt);
- line_buf_ix = 0;
- }
- TERMINAL_ACTION(terminal_action_end)
- {
- /* if not at the end of line */
- if (line_buf_ix < line_len) {
- /* print everything from cursor */
- printf("%s", line_buf + line_buf_ix);
- /* just modify current position */
- line_buf_ix = line_len;
- }
- }
- TERMINAL_ACTION(terminal_action_del)
- {
- terminal_delete_char();
- }
- TERMINAL_ACTION(terminal_action_word_left)
- {
- int old_pos;
- /*
- * Move by word left
- *
- * Are we at the beginning of line?
- */
- if (line_buf_ix <= 0)
- return;
- old_pos = line_buf_ix;
- line_buf_ix--;
- /* skip spaces left */
- while (line_buf_ix && isspace(line_buf[line_buf_ix]))
- line_buf_ix--;
- /* skip all non spaces to the left */
- while (line_buf_ix > 0 &&
- !isspace(line_buf[line_buf_ix - 1]))
- line_buf_ix--;
- /* move cursor to new position */
- terminal_move_cursor(line_buf_ix - old_pos);
- }
- TERMINAL_ACTION(terminal_action_word_right)
- {
- int old_pos;
- /*
- * Move by word right
- *
- * are we at the end of line?
- */
- if (line_buf_ix >= line_len)
- return;
- old_pos = line_buf_ix;
- /* skip all spaces */
- while (line_buf_ix < line_len && isspace(line_buf[line_buf_ix]))
- line_buf_ix++;
- /* skip all non spaces */
- while (line_buf_ix < line_len && !isspace(line_buf[line_buf_ix]))
- line_buf_ix++;
- /*
- * Move cursor to right by printing text
- * between old cursor and new
- */
- if (line_buf_ix > old_pos)
- printf("%.*s", (int) (line_buf_ix - old_pos),
- line_buf + old_pos);
- }
- TERMINAL_ACTION(terminal_action_history_begin)
- {
- terminal_get_line_from_history(-1);
- }
- TERMINAL_ACTION(terminal_action_history_end)
- {
- if (line_index > 0)
- terminal_get_line_from_history(0);
- }
- TERMINAL_ACTION(terminal_action_history_up)
- {
- terminal_get_line_from_history(line_index + 1);
- }
- TERMINAL_ACTION(terminal_action_history_down)
- {
- if (line_index > 0)
- terminal_get_line_from_history(line_index - 1);
- }
- TERMINAL_ACTION(terminal_action_tab)
- {
- /* tab processing */
- process_tab(line_buf, line_buf_ix);
- }
- TERMINAL_ACTION(terminal_action_backspace)
- {
- if (line_buf_ix <= 0)
- return;
- if (line_buf_ix == line_len) {
- printf("\b \b");
- line_len = --line_buf_ix;
- line_buf[line_len] = 0;
- } else {
- putchar('\b');
- line_buf_ix--;
- line_len--;
- memmove(line_buf + line_buf_ix,
- line_buf + line_buf_ix + 1,
- line_len - line_buf_ix + 1);
- printf("%s \b", line_buf + line_buf_ix);
- terminal_move_cursor(line_buf_ix - line_len);
- }
- }
- TERMINAL_ACTION(terminal_action_find_history_forward)
- {
- /* Search history forward */
- terminal_match_hitory(false);
- }
- TERMINAL_ACTION(terminal_action_find_history_backward)
- {
- /* Search history forward */
- terminal_match_hitory(true);
- }
- TERMINAL_ACTION(terminal_action_ctrl_c)
- {
- terminal_clear_line();
- }
- TERMINAL_ACTION(terminal_action_ctrl_d)
- {
- if (line_len > 0) {
- terminal_delete_char();
- } else {
- puts("");
- exit(0);
- }
- }
- TERMINAL_ACTION(terminal_action_clear_screen)
- {
- terminal_clear_screen();
- }
- TERMINAL_ACTION(terminal_action_enter)
- {
- /*
- * On new line add line to history
- * forget history position
- */
- history_add_line(line_buf);
- line_len = 0;
- line_buf_ix = 0;
- line_index = -1;
- /* print new line */
- putchar(c);
- prompt = noprompt;
- process_line(line_buf);
- /* clear current line */
- line_buf[0] = '\0';
- prompt = current_prompt;
- printf("%s", prompt);
- }
- TERMINAL_ACTION(terminal_action_default)
- {
- char str[2] = { c, 0 };
- if (!isprint(c))
- /*
- * TODO: remove this print once all meaningful sequences
- * are identified
- */
- printf("char-0x%02x\n", c);
- else if (line_buf_ix < LINE_BUF_MAX - 1)
- terminal_insert_into_command_line(str);
- }
- /* Callback to call when user hit enter during prompt for */
- static line_callback prompt_callback;
- static KeyAction normal_actions[] = {
- { 0, terminal_action_default },
- { KEY_LEFT, terminal_action_left },
- { KEY_RIGHT, terminal_action_right },
- { KEY_HOME, terminal_action_home },
- { KEY_END, terminal_action_end },
- { KEY_DELETE, terminal_action_del },
- { KEY_CLEFT, terminal_action_word_left },
- { KEY_CRIGHT, terminal_action_word_right },
- { KEY_SUP, terminal_action_history_begin },
- { KEY_SDOWN, terminal_action_history_end },
- { KEY_UP, terminal_action_history_up },
- { KEY_DOWN, terminal_action_history_down },
- { '\t', terminal_action_tab },
- { KEY_BACKSPACE, terminal_action_backspace },
- { KEY_M_n, terminal_action_find_history_forward },
- { KEY_M_p, terminal_action_find_history_backward },
- { KEY_C_C, terminal_action_ctrl_c },
- { KEY_C_D, terminal_action_ctrl_d },
- { KEY_C_L, terminal_action_clear_screen },
- { '\r', terminal_action_enter },
- { '\n', terminal_action_enter },
- { 0, NULL },
- };
- TERMINAL_ACTION(terminal_action_answer)
- {
- putchar(c);
- terminal_set_actions(normal_actions);
- /* Restore default prompt */
- current_prompt = prompt_buf;
- /* No prompt for prints */
- prompt = noprompt;
- line_buf_ix = 0;
- line_len = 0;
- /* Call user function with what was typed */
- prompt_callback(line_buf);
- line_buf[0] = 0;
- /* promot_callback could change current_prompt */
- prompt = current_prompt;
- printf("%s", prompt);
- }
- TERMINAL_ACTION(terminal_action_prompt_ctrl_c)
- {
- printf("^C\n");
- line_buf_ix = 0;
- line_len = 0;
- line_buf[0] = 0;
- current_prompt = prompt_buf;
- prompt = current_prompt;
- terminal_set_actions(normal_actions);
- printf("%s", prompt);
- }
- static KeyAction prompt_actions[] = {
- { 0, terminal_action_default },
- { KEY_LEFT, terminal_action_left },
- { KEY_RIGHT, terminal_action_right },
- { KEY_HOME, terminal_action_home },
- { KEY_END, terminal_action_end },
- { KEY_DELETE, terminal_action_del },
- { KEY_CLEFT, terminal_action_word_left },
- { KEY_CRIGHT, terminal_action_word_right },
- { KEY_BACKSPACE, terminal_action_backspace },
- { KEY_C_C, terminal_action_prompt_ctrl_c },
- { KEY_C_D, terminal_action_ctrl_d },
- { '\r', terminal_action_answer },
- { '\n', terminal_action_answer },
- { 0, NULL },
- };
- void terminal_process_char(int c, line_callback process_line)
- {
- KeyAction *a;
- c = terminal_convert_sequence(c);
- /* Get action for this key */
- a = terminal_get_action(c);
- /* No action found, get default one */
- if (a == NULL)
- a = ¤t_actions[0];
- a->func(c, process_line);
- fflush(stdout);
- }
- void terminal_prompt_for(const char *s, line_callback process_line)
- {
- current_prompt = s;
- if (prompt != noprompt) {
- prompt = s;
- terminal_clear_line();
- }
- prompt_callback = process_line;
- terminal_set_actions(prompt_actions);
- }
- static struct termios origianl_tios;
- static void terminal_cleanup(void)
- {
- tcsetattr(0, TCSANOW, &origianl_tios);
- }
- void terminal_setup(void)
- {
- struct termios tios;
- terminal_set_actions(normal_actions);
- tcgetattr(0, &origianl_tios);
- tios = origianl_tios;
- /*
- * Turn off echo since all editing is done by hand,
- * Ctrl-c handled internally
- */
- tios.c_lflag &= ~(ICANON | ECHO | BRKINT | IGNBRK);
- tcsetattr(0, TCSANOW, &tios);
- /* Restore terminal at exit */
- atexit(terminal_cleanup);
- printf("%s", prompt);
- fflush(stdout);
- }
|