/* * Copyright (C) 2013 Intel Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include #include #include #include #include #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); }