diff --git a/man/tio.1 b/man/tio.1 index 7e22587..52b6b87 100644 --- a/man/tio.1 +++ b/man/tio.1 @@ -99,7 +99,17 @@ Start in hexadecimal mode. .TP .BR \-\-newline-in-hex -Interpret new line characters ('\\r', '\\n') in hexadecimal mode. +Interpret new line characters ('\er', '\en') in hexadecimal mode. + +.TP +.BR \-\-line-edit + +Start in line editing mode. + +.TP +.BR \-\-no-newline-in-line-edit + +Does not append "\er\en" after a line in line editing mode. .TP .BR \-v ", " \-\-version @@ -150,6 +160,62 @@ By default there is \fBno new line\fR in this mode, but it can be turned on usin .TP Bytes can be sent in this mode by typing the \fBtwo-character hexadecimal\fR representation of the value, e.g.: to send \fI0xA\fR you must type \fI0a\fR or \fI0A\fR. +.SH "LINE EDIT" +The program can be started in line editing mode using the \fB--line-edot\fR option. In this mode, the current line can be edited by inserting/deleting characters. Escape values can be used for bytes. +.PP +.TP 16n +The following keys can be used: + +.IP "\fB" +Add character to the position of the cursor. + +.IP "\fBRIGHT\fR, \fBLEFT\fR" +Move cursor in the line. + +.IP "\fBUP\fR, \fBDOWN\fR" +Get prevoiusly sent lines from the history. + +.IP "\fBBACKSPACE\fR" +Delete character before the cursor. + +.IP "\fBENTER\fR" +Send line. + +.PP +.TP 16n +The following commands can be used: + +.IP "\fB:?\fR" +List available commands. + +.IP "\fB:q\fR" +Quit. + +.IP "\fB:v\fR" +Show version. + +.IP "\fB::\fR" +Send ':'. + +.PP +.TP 16n +Escape bytes: + +.IP \fB\edNNN\fR +Send NNN as a decimal value. NNN must be 3 characters and less than or equal to 255. For example: \ed023 = 23. + +.IP \fB\exNN\fR +Send NN as a hexadecimal value. NN must be 2 characters. For example: \exff = 255. + +.IP \fB\ebNNNNNNNN\fR +Send NNNNNNNN as a binary value. NNNNNNNN must be 8 characters. For example: \eb0000001 = 1. + +.TP 7n +The following line sends the ASCII string \fI"Hello"\fR: + +H\ed101\ex6c\eb01101100o + + .SH "EXAMPLES" .TP Typical use is without options. For example: diff --git a/src/Makefile.am b/src/Makefile.am index 046c599..d10fccc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,9 @@ tio_SOURCES = tty.c \ include/tio/time.h \ include/tio/print.h \ include/tio/log.h \ - include/tio/error.h + include/tio/error.h \ + lineedit.c \ + include/tio/lineedit.h if ADD_SETSPEED2 tio_SOURCES += setspeed2.c diff --git a/src/bash-completion/tio.in b/src/bash-completion/tio.in index 22c8080..7da00af 100644 --- a/src/bash-completion/tio.in +++ b/src/bash-completion/tio.in @@ -17,8 +17,14 @@ _tio() -p --parity \ -o --output-delay \ -n --no-autoconnect \ + -e --local-echo \ + -t --timestamp \ -l --log \ -m --map \ + -x --hex \ + --newline-in-hex \ + --line-edit \ + --no-newline-in-line-edit \ -v --version \ -t --timestamp \ -h --help" @@ -70,6 +76,22 @@ _tio() COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 ;; + -x | --hex) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + --newline-in-hex) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + --line-edit) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + --no-newline-in-line-edit) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; -v | --version) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 diff --git a/src/include/tio/lineedit.h b/src/include/tio/lineedit.h new file mode 100644 index 0000000..bbc792c --- /dev/null +++ b/src/include/tio/lineedit.h @@ -0,0 +1,40 @@ +#ifndef LINEEDIT_H +#define LINEEDIT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LINE_SIZE 81 /* 80 chars + terminating */ + +#define GOTOXY(x,y) fprintf(stdout, "\033[%d;%dH", (y), (x)) + +#define ACTION_PREFIX_1 0x1b +#define ACTION_PREFIX_2 0x5b +#define ARROW_LEFT 0x44 +#define ARROW_RIGHT 0x43 +#define ARROW_UP 0x41 +#define ARROW_DOWN 0x42 + +#define BACKSPACE 0x7f + +void lineedit_configure(const char * prefix, size_t max_len); +void add_to_history(const char * line); +char * get_line(char c); +void free_line(char * line); + +#endif diff --git a/src/include/tio/options.h b/src/include/tio/options.h index e6b933c..90d631d 100644 --- a/src/include/tio/options.h +++ b/src/include/tio/options.h @@ -27,7 +27,9 @@ #include #include -#define OPT_NEWLINE_IN_HEX 1000 // "short" option for --newline-in-hex +#define OPT_NEWLINE_IN_HEX 1000 // "short" option for --newline-in-hex +#define OPT_LINE_EDIT 1001 // "short" option for --line-edit +#define OPT_NO_NEWLINE_LINE_EDIT 1002 // "short" option for --no-newline-in-line-edit /* Options */ struct option_t @@ -45,6 +47,8 @@ struct option_t bool timestamp; bool hex_mode; bool newline_in_hex; + bool line_edit; + bool no_newline_in_line_edit; const char *log_filename; const char *map; }; diff --git a/src/lineedit.c b/src/lineedit.c new file mode 100644 index 0000000..f643c81 --- /dev/null +++ b/src/lineedit.c @@ -0,0 +1,192 @@ +#include "tio/lineedit.h" +#include "tio/options.h" +#include "tio/print.h" + +static struct winsize winsize; + +static char edited_line[LINE_SIZE]; +static unsigned char edited_line_ctr = 0; +static int edited_line_cursor_pos = 0; +static const char * line_edit_prefix; +static int line_edit_prefix_length; +static char prev_char = 0, prevprev_char = 0; + +static char ** history = NULL; +static unsigned int history_max_len; +static unsigned int history_len = 0; +static unsigned int history_pointer = 0; + +static void free_history(void){ + for(unsigned int i = 0; i < history_len; i++){ + if(history[i]){ + free(history[i]); + } + } + + free(history); +} + +static char * get_from_history(int dir){ + char * ret = NULL; + + if(dir == 1){ + ret = history[history_pointer]; + + if(history_pointer + 1 < history_len){ + history_pointer++; + } + } else if(dir == -1){ + if(history_pointer > 0){ + history_pointer--; + } + + ret = history[history_pointer]; + } + + return ret; +} + +void lineedit_configure(const char * prefix, size_t max_len){ + if(ioctl(0, TIOCGWINSZ, &winsize) < 0){ + warning_printf("Could not get the dimensions of the terminal"); + exit(EXIT_FAILURE); + } + + history_max_len = max_len; + history = (char **) calloc(history_max_len, sizeof(char *)); + if(history == NULL){ + warning_printf("Could not allocate the history"); + exit(EXIT_FAILURE); + } + + atexit(free_history); + + line_edit_prefix = prefix; + line_edit_prefix_length = strlen(prefix); +} + +char * get_line(char c){ + char * finished_line = NULL; + char * history_line = NULL; + + if(prev_char == ACTION_PREFIX_1 && c == ACTION_PREFIX_2){ + /* do nothing */ + } else if(prevprev_char == ACTION_PREFIX_1 && prev_char == ACTION_PREFIX_2){ + switch(c){ + case ARROW_LEFT: + if(edited_line_cursor_pos > 0){ + edited_line_cursor_pos--; + } + break; + + case ARROW_RIGHT: + if(edited_line_cursor_pos < edited_line_ctr){ + edited_line_cursor_pos++; + } + + break; + + case ARROW_UP: + if((history_line = get_from_history(1)) != NULL){ + strcpy(edited_line, history_line); + edited_line_ctr = strlen(history_line); + edited_line_cursor_pos = edited_line_ctr; + } + break; + + case ARROW_DOWN: + if((history_line = get_from_history(-1)) != NULL){ + strcpy(edited_line, history_line); + edited_line_ctr = strlen(history_line); + edited_line_cursor_pos = edited_line_ctr; + } + break; + } + } else { + if(c == '\r' || c == '\n'){ + edited_line[edited_line_ctr] = '\0'; + finished_line = strdup(edited_line); + } else { + if(isprint(c)){ + if(edited_line_ctr < LINE_SIZE - 1){ + memmove(&edited_line[edited_line_cursor_pos + 1], + &edited_line[edited_line_cursor_pos], + edited_line_ctr - edited_line_cursor_pos); + + edited_line[edited_line_cursor_pos] = c; + + edited_line_ctr++; + edited_line_cursor_pos++; + } + } else if(c == BACKSPACE){ + char * rest = &edited_line[edited_line_cursor_pos]; + memcpy(&edited_line[edited_line_cursor_pos - 1], rest, edited_line_ctr - edited_line_cursor_pos); + + if(edited_line_ctr > 0){ + edited_line_ctr--; + } + + if(edited_line_cursor_pos > 0){ + edited_line_cursor_pos--; + } + } + } + } + + /* clear line with spaces */ + fprintf(stdout, "\r%*c", winsize.ws_col, ' '); + /* output the contents of the buffer */ + fprintf(stdout, "\r%s%.*s", line_edit_prefix, edited_line_ctr, edited_line); + + /* set the position of the cursor */ + GOTOXY(edited_line_cursor_pos + line_edit_prefix_length + 1, winsize.ws_row); + + fflush(stdout); + + /* clear buffer */ + if(finished_line != NULL){ + memset(edited_line, 0, LINE_SIZE); + edited_line_ctr = 0; + edited_line_cursor_pos = 0; + } + + prevprev_char = prev_char; + prev_char = c; + + return finished_line; +} + +void free_line(char * line){ + free(line); + fprintf(stdout, "\r%s", line_edit_prefix); +} + +void add_to_history(const char * line){ + /* dont add duplicate duplicate */ + if(history_len > 0 && strcmp(line, history[0]) == 0){ + return; + } + + /* history is full, make room */ + if(history_len == history_max_len){ + free(history[history_len - 1]); + } + + /* shift history to the right by 1 */ + size_t count = history_len == history_max_len ? history_len - 1 : history_len; + memmove(&history[1], &history[0], count * sizeof(char *)); + + /* add to history */ + history[0] = strdup(line); + if(history[0] == NULL){ + return; + } + + if(history_len < history_max_len){ + history_len++; + } + + history_pointer = 0; +} + + diff --git a/src/linenoise b/src/linenoise new file mode 160000 index 0000000..97d2850 --- /dev/null +++ b/src/linenoise @@ -0,0 +1 @@ +Subproject commit 97d2850af13c339369093b78abe5265845d78220 diff --git a/src/main.c b/src/main.c index 83c060a..515f1ce 100644 --- a/src/main.c +++ b/src/main.c @@ -28,6 +28,7 @@ #include "tio/log.h" #include "tio/error.h" #include "tio/print.h" +#include "tio/lineedit.h" int main(int argc, char *argv[]) { @@ -50,7 +51,10 @@ int main(int argc, char *argv[]) /* Install log exit handler */ atexit(&log_exit); - + + if(option.line_edit) + lineedit_configure(">", 100); + /* Create log file */ if (option.log) log_open(option.log_filename); diff --git a/src/options.c b/src/options.c index dd62bb5..e71a04b 100644 --- a/src/options.c +++ b/src/options.c @@ -49,6 +49,8 @@ struct option_t option = false, // No timestamp false, // Not starting in hex mode false, // No newlines in hex mode + false, // Not starting in line edit mode + false, // Send new line in line edit mode "", // Log filename "" // Map string }; @@ -70,13 +72,16 @@ void print_help(char *argv[]) printf(" -l, --log Log to file\n"); printf(" -m, --map Map special characters\n"); printf(" -x, --hex Start in hexadecimal mode\n"); - printf(" --newline-in-hex Interpret new line characters in hex mode\n"); + printf(" --newline-in-hex Interpret new line characters in hex mode\n"); + printf(" --line-edit Start in line edit mode\n"); + printf(" --no-newline-in-line-edit Don't send newline after a line in line edit mode\n"); printf(" -v, --version Display version\n"); printf(" -h, --help Display help\n"); printf("\n"); printf("See the man page for list of supported mapping flags.\n"); printf("\n"); - printf("In session, press ctrl-t q to quit.\n"); + printf("In session, press ctrl-t q to quit.\n"); + printf("In line edit mode, type :q to quit.\n"); printf("\n"); } @@ -110,23 +115,27 @@ void parse_options(int argc, char *argv[]) { static struct option long_options[] = { - {"baudrate", required_argument, 0, 'b'}, - {"databits", required_argument, 0, 'd'}, - {"flow", required_argument, 0, 'f'}, - {"stopbits", required_argument, 0, 's'}, - {"parity", required_argument, 0, 'p'}, - {"output-delay", required_argument, 0, 'o'}, - {"no-autoconnect", no_argument, 0, 'n'}, - {"local-echo", no_argument, 0, 'e'}, - {"timestamp", no_argument, 0, 't'}, - {"log", required_argument, 0, 'l'}, - {"map", required_argument, 0, 'm'}, - {"hex", no_argument, 0, 'x'}, - {"newline-in-hex", no_argument, 0, OPT_NEWLINE_IN_HEX }, - {"version", no_argument, 0, 'v'}, - {"help", no_argument, 0, 'h'}, - {0, 0, 0, 0 } + {"baudrate", required_argument, 0, 'b'}, + {"databits", required_argument, 0, 'd'}, + {"flow", required_argument, 0, 'f'}, + {"stopbits", required_argument, 0, 's'}, + {"parity", required_argument, 0, 'p'}, + {"output-delay", required_argument, 0, 'o'}, + {"no-autoconnect", no_argument, 0, 'n'}, + {"local-echo", no_argument, 0, 'e'}, + {"timestamp", no_argument, 0, 't'}, + {"log", required_argument, 0, 'l'}, + {"map", required_argument, 0, 'm'}, + {"hex", no_argument, 0, 'x'}, + {"newline-in-hex", no_argument, 0, OPT_NEWLINE_IN_HEX }, + {"line-edit", no_argument, 0, OPT_LINE_EDIT }, + {"no-newline-in-line-edit", no_argument, 0, OPT_NO_NEWLINE_LINE_EDIT }, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0 } }; + + #define OPT_NO_NEWLINE_LINE_EDIT 1002 // "short" option for --no-newline-in-line-edit /* getopt_long stores the option index here */ int option_index = 0; @@ -202,6 +211,14 @@ void parse_options(int argc, char *argv[]) case OPT_NEWLINE_IN_HEX: option.newline_in_hex = true; break; + + case OPT_LINE_EDIT: + option.line_edit = true; + break; + + case OPT_NO_NEWLINE_LINE_EDIT: + option.no_newline_in_line_edit = true; + break; case 'v': printf("tio v%s\n", VERSION); diff --git a/src/tty.c b/src/tty.c index ba3a5d3..fc3becf 100644 --- a/src/tty.c +++ b/src/tty.c @@ -43,11 +43,14 @@ #include "tio/time.h" #include "tio/log.h" #include "tio/error.h" +#include "tio/lineedit.h" #ifdef HAVE_TERMIOS2 extern int setspeed2(int fd, int baudrate); #endif +#define ESCAPED_BUFFER_SIZE 9 // max 8 binary digit + terminating 0 + static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; static unsigned long rx_total = 0, tx_total = 0; static bool connected = false; @@ -60,9 +63,14 @@ static bool map_i_nl_crnl = false; static bool map_o_cr_nl = false; static bool map_o_nl_crnl = false; static bool map_o_del_bs = false; -static char hex_chars[2]; +static char hex_chars[3] = { 0 }; static unsigned char hex_char_index = 0; +static char parsed_line[LINE_SIZE]; +static unsigned int parsed_line_ctr; + +void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward); + #define tio_printf(format, args...) \ { \ if (tainted) putchar('\n'); \ @@ -80,30 +88,28 @@ static void optional_local_echo(char c) log_write(c); } + +inline static bool is_valid_bin(char c) +{ + return (c >= '0' && c <= '1'); +} + inline static bool is_valid_hex(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } - -inline static unsigned char char_to_nibble(char c) -{ - if(c >= '0' && c <= '9'){ - return c - '0'; - } else if(c >= 'a' && c <= 'f'){ - return c - 'a' + 10; - } else if(c >= 'A' && c <= 'F'){ - return c - 'A' + 10; - } else { - return 0; - } -} +inline static bool is_valid_dec(char c) +{ + return (c >= '0' && c <= '9'); +} + static void output_hex(char c) { - hex_chars[hex_char_index++] = c; + hex_chars[hex_char_index++] = c; - if(hex_char_index == 2){ - unsigned char hex_value = char_to_nibble(hex_chars[0]) << 4 | (char_to_nibble(hex_chars[1]) & 0x0F); + if(hex_char_index == 2){ + unsigned char hex_value = (unsigned char)strtol(hex_chars, NULL, 16); hex_char_index = 0; optional_local_echo(hex_value); @@ -135,6 +141,162 @@ static void print_normal(char c) fflush(stdout); } +static void send_line(const char * line) +{ + int len = strlen(line); + + if(option.local_echo){ + fprintf(stdout, "\r\n"); + for(int i = 0; i < len; i++){ + optional_local_echo(line[i]); + } + + if(option.no_newline_in_line_edit){ + fprintf(stdout, "\r\n"); + } + } else { + get_line(0); + } + + ssize_t status = write(fd, line, len); + if (status < 0){ + warning_printf("Could not write to tty device"); + } else { + tx_total += len; + } +} + +static void parse_line(const char * line) +{ + int len = strlen(line); + + if(len == 0){ + return; + } + + /* escaped characters will collapse */ + if(len > LINE_SIZE - 3){ + warning_printf("Line is too long (%d): %s", len, line); + return; + } + + parsed_line_ctr = 0; + memset(parsed_line, 0, LINE_SIZE); + + /* handle commands */ + if(len == 2 && line[0] == ':'){ + switch(line[1]){ + case '?': + tio_printf("Commands:"); + tio_printf(":? List available commands"); + tio_printf(":q Quit"); + tio_printf(":v Show version"); + tio_printf(":: Send ':'"); + break; + + case 'q': + handle_command_sequence(KEY_Q, KEY_CTRL_T, NULL, NULL); + break; + + case 'v': + handle_command_sequence(KEY_V, KEY_CTRL_T, NULL, NULL); + break; + + case ':': + parsed_line[parsed_line_ctr++] = ':'; + break; + + default: + warning_printf("Unknown command: %c", line[1]); + return; + + } + /* interpret line */ + } else { + int characters_to_read = 0; + char buffer[ESCAPED_BUFFER_SIZE]; + unsigned int buffer_ctr = 0; + int base = 10; + memset(buffer, 0, ESCAPED_BUFFER_SIZE); + + unsigned long value; + + /* handle escape characters */ + for(unsigned int i = 0; i < len; i++){ + if(characters_to_read > 0){ + if(base == 16 && !is_valid_hex(line[i])){ + warning_printf("Invalid hexadecimal character: %c", line[i]); + return; + } else if(base == 10 && !is_valid_dec(line[i])){ + warning_printf("Invalid decimal character: %c", line[i]); + return; + } else if(base == 2 && !is_valid_bin(line[i])){ + warning_printf("Invalid binary character: %c", line[i]); + return; + } else { + buffer[buffer_ctr++] = line[i]; + + if(buffer_ctr == characters_to_read){ + value = (unsigned long)strtol(buffer, NULL, base); + + if(value > 255){ + warning_printf("Value is too large for a byte: %lu", value); + return; + } + + parsed_line[parsed_line_ctr++] = (unsigned char)value; + + memset(buffer, 0, ESCAPED_BUFFER_SIZE); + buffer_ctr = 0; + characters_to_read = 0; + } + } + } else { + if(line[i] == '\\'){ + if(i + 1 < len && line[i+1] == '\\'){ + parsed_line[parsed_line_ctr++] = '\\'; + i++; + } else if(i + 1 < len && line[i+1] == 'x'){ + base = 16; + characters_to_read = 2; + i++; + } else if(i + 1 < len && line[i+1] == 'd'){ + base = 10; + characters_to_read = 3; + i++; + } else if(i + 1 < len && line[i+1] == 'b'){ + base = 2; + characters_to_read = 8; + i++; + } else if(i + 1 < len){ + warning_printf("Unknown base: %c", line[i+1]); + return; + } else { + warning_printf("Invalid escape: %s", line); + return; + } + } else { + parsed_line[parsed_line_ctr++] = line[i]; + } + } + } + + if(characters_to_read != 0){ + warning_printf("Unescaped line: %s", line); + return; + } + + if(option.no_newline_in_line_edit == false){ + parsed_line[parsed_line_ctr++] = '\r'; + parsed_line[parsed_line_ctr++] = '\n'; + } + + parsed_line[parsed_line_ctr++] = '\0'; + } + + send_line(parsed_line); +} + static void toggle_line(const char *line_name, int mask) { int state; @@ -375,7 +537,12 @@ void stdout_configure(void) /* Print launch hints */ tio_printf("tio v%s", VERSION); - tio_printf("Press ctrl-t q to quit"); + if(option.line_edit) + { + tio_printf("Type :q to quit, :? for help."); + } else { + tio_printf("Press ctrl-t q to quit, ctrl-t ? for help."); + } /* At start use normal print function */ print = print_normal; @@ -661,6 +828,7 @@ int tty_connect(void) int status; time_t next_timestamp = 0; char* now = NULL; + char* raw_line = NULL; /* Open tty device */ #ifdef __APPLE__ @@ -701,17 +869,17 @@ int tty_connect(void) next_timestamp = time(NULL); if (option.hex_mode) - { - print = print_hex; - print_mode = HEX; - tio_printf("Switched to hexadecimal mode"); - } - else - { - print = print_normal; - print_mode = NORMAL; - tio_printf("Switched to normal mode"); - } + { + print = print_hex; + print_mode = HEX; + tio_printf("Switched to hexadecimal mode"); + } + else + { + print = print_normal; + print_mode = NORMAL; + tio_printf("Switched to normal mode"); + } /* Save current port settings */ if (tcgetattr(fd, &tio_old) < 0) @@ -744,153 +912,172 @@ int tty_connect(void) #endif maxfd = MAX(fd, STDIN_FILENO) + 1; /* Maximum bit entry (fd) to test */ - - /* Input loop */ - while (true) + + /* display initial prompt */ + if(option.line_edit) { - FD_ZERO(&rdfs); - FD_SET(fd, &rdfs); - FD_SET(STDIN_FILENO, &rdfs); - - /* Block until input becomes available */ - status = select(maxfd, &rdfs, NULL, NULL, NULL); - if (status > 0) - { - if (FD_ISSET(fd, &rdfs)) - { - /* Input from tty device ready */ - if (read(fd, &input_char, 1) > 0) - { - /* Update receive statistics */ - rx_total++; - - /* Print timestamp on new line, if desired. */ - if (next_timestamp && input_char != '\n' && input_char != '\r') - { - now = current_time(); - fprintf(stdout, ANSI_COLOR_GRAY "[%s] " ANSI_COLOR_RESET, now); - if (option.log) - { - log_write('['); - while (*now != '\0') - { - log_write(*now); - ++now; - } - log_write(']'); - log_write(' '); - } - next_timestamp = 0; - } - - /* Map input character */ - if ((input_char == '\n') && (map_i_nl_crnl)) - { - print('\r'); - print('\n'); - if (option.timestamp) - next_timestamp = time(NULL); - } else - { - /* Print received tty character to stdout */ - print(input_char); - } - fflush(stdout); - - /* Write to log */ - if (option.log) - log_write(input_char); - - tainted = true; - - if (input_char == '\n' && option.timestamp) - next_timestamp = time(NULL); - } else - { - /* Error reading - device is likely unplugged */ - error_printf_silent("Could not read from tty device"); - goto error_read; - } - } - - if (FD_ISSET(STDIN_FILENO, &rdfs)) - { - bool forward = true; - - /* Input from stdin ready */ - status = read(STDIN_FILENO, &input_char, 1); - if (status <= 0) - { - error_printf_silent("Could not read from stdin"); - goto error_read; - } - - /* Forward input to output except ctrl-t key */ - output_char = input_char; - if (input_char == KEY_CTRL_T) - forward = false; - - /* Handle commands */ - handle_command_sequence(input_char, previous_char, &output_char, &forward); - - if (forward) - { - if(print_mode == HEX){ - if(!is_valid_hex(input_char)){ - warning_printf("Invalid hex character: '%c' (0x%02x)", input_char, input_char); - continue; - } - } - - /* Map output character */ - if ((output_char == 127) && (map_o_del_bs)) - output_char = '\b'; - if ((output_char == '\r') && (map_o_cr_nl)) - output_char = '\n'; - - /* Map newline character */ - if ((output_char == '\n') && (map_o_nl_crnl)) { - char r = '\r'; - - optional_local_echo(r); - status = write(fd, &r, 1); - if (status < 0) - warning_printf("Could not write to tty device"); - - tx_total++; - delay(option.output_delay); - } - - if(print_mode == HEX){ - output_hex(output_char); - } else { - /* Send output to tty device */ - optional_local_echo(output_char); - - status = write(fd, &output_char, 1); - if (status < 0) - warning_printf("Could not write to tty device"); - fsync(fd); - - /* Update transmit statistics */ - tx_total++; - } - - /* Insert output delay */ - delay(option.output_delay); - } - - /* Save previous key */ - previous_char = input_char; - - } - } else if (status == -1) - { - error_printf("Error: select() failed (%s)", strerror(errno)); - exit(EXIT_FAILURE); - } + get_line(0); } + /* Input loop */ + while (true) + { + FD_ZERO(&rdfs); + FD_SET(fd, &rdfs); + FD_SET(STDIN_FILENO, &rdfs); + + /* Block until input becomes available */ + status = select(maxfd, &rdfs, NULL, NULL, NULL); + if (status > 0) + { + if (FD_ISSET(fd, &rdfs)) + { + /* Input from tty device ready */ + if (read(fd, &input_char, 1) > 0) + { + /* Update receive statistics */ + rx_total++; + + /* Print timestamp on new line, if desired. */ + if (next_timestamp && input_char != '\n' && input_char != '\r') + { + now = current_time(); + fprintf(stdout, ANSI_COLOR_GRAY "[%s] " ANSI_COLOR_RESET, now); + if (option.log) + { + log_write('['); + while (*now != '\0') + { + log_write(*now); + ++now; + } + log_write(']'); + log_write(' '); + } + next_timestamp = 0; + } + + // FIXME: make it better in line edit mode + /* Map input character */ + if ((input_char == '\n') && (map_i_nl_crnl)) + { + print('\r'); + print('\n'); + if (option.timestamp) + next_timestamp = time(NULL); + } else + { + /* Print received tty character to stdout */ + print(input_char); + } + fflush(stdout); + + /* Write to log */ + if (option.log) + log_write(input_char); + + tainted = true; + + if (input_char == '\n' && option.timestamp) + next_timestamp = time(NULL); + } else + { + /* Error reading - device is likely unplugged */ + error_printf_silent("Could not read from tty device"); + goto error_read; + } + } + + if (FD_ISSET(STDIN_FILENO, &rdfs)) + { + bool forward = true; + + /* Input from stdin ready */ + status = read(STDIN_FILENO, &input_char, 1); + if (status <= 0) + { + error_printf_silent("Could not read from stdin"); + goto error_read; + } + + if(option.line_edit){ + if((raw_line = get_line(input_char)) != NULL){ + add_to_history(raw_line); + + parse_line(raw_line); + + free_line(raw_line); + } + } else { + /* Forward input to output except ctrl-t key */ + output_char = input_char; + if (input_char == KEY_CTRL_T) + forward = false; + + /* Handle commands */ + handle_command_sequence(input_char, previous_char, &output_char, &forward); + + if (forward) + { + if(print_mode == HEX){ + if(!is_valid_hex(input_char)){ + warning_printf("Invalid hex character: '%c' (0x%02x)", input_char, input_char); + hex_char_index = 0; + continue; + } + } + + /* Map output character */ + if ((output_char == 127) && (map_o_del_bs)) + output_char = '\b'; + if ((output_char == '\r') && (map_o_cr_nl)) + output_char = '\n'; + + /* Map newline character */ + if ((output_char == '\n') && (map_o_nl_crnl)) { + char r = '\r'; + + optional_local_echo(r); + status = write(fd, &r, 1); + if (status < 0) + warning_printf("Could not write to tty device"); + + tx_total++; + delay(option.output_delay); + } + + + if(print_mode == HEX){ + output_hex(output_char); + } else { + /* Send output to tty device */ + optional_local_echo(output_char); + + status = write(fd, &output_char, 1); + if (status < 0) + warning_printf("Could not write to tty device"); + fsync(fd); + + /* Update transmit statistics */ + tx_total++; + } + + /* Insert output delay */ + delay(option.output_delay); + } + + /* Save previous key */ + previous_char = input_char; + + } + } + } else if (status == -1) + { + error_printf("Error: select() failed (%s)", strerror(errno)); + exit(EXIT_FAILURE); + } + } + return TIO_SUCCESS; #ifdef HAVE_TERMIOS2