/* * tio - a serial device I/O tool * * Copyright (c) 2014-2022 Martin Lund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #if defined(__linux__) #include #endif #if defined(__APPLE__) || defined(__MACH__) #include #include #include #include #include #endif #include "version.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "configfile.h" #include "tty.h" #include "print.h" #include "options.h" #include "misc.h" #include "log.h" #include "error.h" #include "socket.h" #include "setspeed.h" #include "rs485.h" #include "alert.h" #include "timestamp.h" #include "misc.h" #include "script.h" #include "xymodem.h" #include "fs.h" #include "readline.h" /* tty device listing configuration */ #if defined(__linux__) #define PATH_SERIAL_DEVICES "/dev" #define PATH_SERIAL_DEVICES_BY_ID "/dev/serial/by-id" #define PATH_SERIAL_DEVICES_BY_PATH "/dev/serial/by-path" #elif defined(__FreeBSD__) #define PATH_SERIAL_DEVICES "/dev" #elif defined(__APPLE__) #define PATH_SERIAL_DEVICES "/dev" #elif defined(__CYGWIN__) #define PATH_SERIAL_DEVICES "/dev" #elif defined(__HAIKU__) #define PATH_SERIAL_DEVICES "/dev/ports" #else #define PATH_SERIAL_DEVICES "/dev" #endif #ifndef CMSPAR #define CMSPAR 010000000000 #endif #define KEY_0 0x30 #define KEY_1 0x31 #define KEY_2 0x32 #define KEY_3 0x33 #define KEY_4 0x34 #define KEY_5 0x35 #define KEY_6 0x36 #define KEY_7 0x37 #define KEY_8 0x38 #define KEY_9 0x39 #define KEY_QUESTION 0x3f #define KEY_A 0x61 #define KEY_B 0x62 #define KEY_C 0x63 #define KEY_E 0x65 #define KEY_F 0x66 #define KEY_SHIFT_F 0x46 #define KEY_G 0x67 #define KEY_I 0x69 #define KEY_J 0x6A #define KEY_SHIFT_J 0x4A #define KEY_K 0x6B #define KEY_L 0x6C #define KEY_SHIFT_L 0x4C #define KEY_M 0x6D #define KEY_O 0x6F #define KEY_P 0x70 #define KEY_Q 0x71 #define KEY_R 0x72 #define KEY_SHIFT_R 0x52 #define KEY_S 0x73 #define KEY_T 0x74 #define KEY_V 0x76 #define KEY_X 0x78 #define KEY_Y 0x79 #define KEY_Z 0x7a typedef enum { LINE_TOGGLE, LINE_PULSE } tty_line_mode_t; typedef enum { SUBCOMMAND_NONE, SUBCOMMAND_LINE_TOGGLE, SUBCOMMAND_LINE_PULSE, SUBCOMMAND_XMODEM, SUBCOMMAND_MAP, } sub_command_t; #define MLINE_MAX 4096 // clang-format off const char random_array[] = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x20, 0x28, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x29, 0x20, 0x29, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7C, 0x5D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2F, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x2D, 0x2D, 0x2D, 0x2D, 0x27, 0x0A, 0x0A, 0x54, 0x69, 0x6D, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x63, 0x6F, 0x66, 0x66, 0x65, 0x65, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6B, 0x21, 0x0A, 0x20, 0x0A, 0x00 }; // clang-format on bool interactive_mode = true; state_t state = STATE_STARTING; char key_hit = 0xff; const char* device_name = NULL; GList *device_list = NULL; static struct termios tio, tio_raw, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; unsigned long rx_total = 0, tx_total = 0; static bool connected = false; static bool standard_baudrate = true; static void (*printchar)(char c); static int device_fd; static char hex_chars[2]; static unsigned char hex_char_index = 0; static char tty_buffer[BUFSIZ*2]; static size_t tty_buffer_count = 0; static char *tty_buffer_write_ptr = tty_buffer; static pthread_t thread; static int pipefd[2]; static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER; static char line[PATH_MAX], mline[MLINE_MAX]; static size_t listing_device_name_length_max = 0; static readline_t *readline_ctx = NULL; static readline_t *subcmd_readline_ctx = NULL; static void optional_local_echo(char c) { if (!option.local_echo) { return; } printchar(c); if ((option.output_mode == OUTPUT_MODE_NORMAL) && (c == 127)) { // Force destructive backspace printf("\b \b"); } if (option.log) { log_putc(c); } } 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; } } raw_t tty_get_raw_mode(void) { switch (state) { case STATE_INTERACTIVE: return option.raw_interactive; case STATE_STARTING: case STATE_PIPED_INPUT: case STATE_EXEC_SHELL_COMMAND: case STATE_XYMODEM: default: return option.raw; } } int tty_tcsetattr(int fd) { if ( ! connected ) { return -1; } /* Activate or change port settings */ int status; raw_t raw = tty_get_raw_mode(); if (raw == RAW_OFF) { status = tcsetattr(fd, TCSANOW, &tio); } else { status = tcsetattr(fd, TCSANOW, &tio_raw); } if (status == -1) { tio_error_printf_silent("Could not apply port settings (%s)", strerror(errno)); return -1; } /* Set arbitrary baudrate (only works on supported platforms) */ if (!standard_baudrate) { if (setspeed(device_fd, option.baudrate) != 0) { tio_error_printf_silent("Could not set baudrate speed (%s)", strerror(errno)); return -1; } } return 0; } void tty_sync(int fd) { /* If output_delay is valid, tty_buffer should be already empty. * So this function doesn't consider output_delay options. */ ssize_t count; size_t remain = tty_buffer_count; char *cp = tty_buffer; while (remain > 0) { count = write_poll(fd, cp, remain, POLL_FOREVER); if (count < 0) { // Error tio_debug_printf("Write error while flushing tty buffer (%s)", strerror(errno)); break; } cp += count; remain -= count; // Update transmit statistics tx_total += count; // Reduce the number of additional writes if (remain > 0) { int estimated_sendtime_us = (int)((int64_t)count * 10 * 1000 * 1000 / option.baudrate); if (estimated_sendtime_us > 300 * 1000) usleep(300 * 1000); else usleep(estimated_sendtime_us); } } fsync(fd); tcdrain(fd); // Reset tty_buffer_write_ptr = tty_buffer; tty_buffer_count = 0; } static ssize_t tty_raw_write(int fd) { raw_t raw = tty_get_raw_mode(); if ((raw == RAW_ON_NODELAY) || ((raw == RAW_ON_DELAY) && ( ! option.output_delay )) || ((raw == RAW_OFF) && ( ! (option.output_delay || option.output_line_delay || option.map_o_nulbrk) ))) { return 0; // No-op in this function, write in tty_sync() } // Write byte by byte with output delay and null break ssize_t retval = 0; size_t i; for (i = 0; i < tty_buffer_count; i++) { if ((raw == RAW_OFF) && (tty_buffer[i] == 0) && (option.map_o_nulbrk)) { retval = tcsendbreak(fd, 0); if (retval < 0) { tio_warning_printf("Could not send break"); goto error; } continue; } retval = write_poll(fd, &tty_buffer[i], 1, POLL_FOREVER); if (retval < 0) { // Error tio_debug_printf("Write error (%s)", strerror(errno)); goto error; } fsync(fd); tcdrain(fd); // Update transmit statistics tx_total++; if ((raw == RAW_OFF) && (option.output_line_delay) && (tty_buffer[i] == option.output_line_delay_char)) { delay(option.output_line_delay); } else if (option.output_delay) { delay(option.output_delay); } } retval = tty_buffer_count; // Path-through error: // Reset tty_buffer_write_ptr = tty_buffer; tty_buffer_count = 0; return retval; } ssize_t tty_write(int fd, const void *buffer, size_t count) { int status; const char *cp = (char *)buffer; size_t i; raw_t raw = tty_get_raw_mode(); if (raw != RAW_OFF) { /* RAW mode */ for (i = 0; i < count; i++, cp++) { if (tty_buffer_count >= BUFSIZ) { status = tty_raw_write(fd); if (status < 0) { return status; } tty_sync(fd); } *tty_buffer_write_ptr = *cp; tty_buffer_write_ptr++; tty_buffer_count++; } } else { /* not RAW mode */ for (i = 0; i < count; i++, cp++) { if (tty_buffer_count >= BUFSIZ) { status = tty_raw_write(fd); if (status < 0) { return status; } tty_sync(fd); } /* Map output character */ char *tp; int bytes_add; bytes_add = -1; /* negative value means "not mapped yet" */ tp = tty_buffer_write_ptr; if ((*cp == 127) && (option.map_o_del_bs)) { *tp = '\b'; bytes_add = 1; } if ((*cp == '\r') && (option.map_o_cr_nl)) { *tp = '\n'; bytes_add = 1; } if ((*cp == '\r') && (option.map_o_ign_cr)) { bytes_add = 0; } if ((*cp == '\n' || *cp == '\r') && (option.map_o_nl_crnl)) { *tp = '\r'; *(tp + 1) = '\n'; bytes_add = 2; } if (bytes_add < 0) { *tp = (option.map_o_ltu) ? toupper(*cp) : *cp; bytes_add = 1; } if (bytes_add > 0) { tty_buffer_write_ptr += bytes_add; tty_buffer_count += bytes_add; } } } status = tty_raw_write(fd); if (status < 0) { return status; } return count; } void *tty_stdin_input_thread(void *arg) { UNUSED(arg); char input_buffer[BUFSIZ]; ssize_t byte_count; ssize_t bytes_written; // Create FIFO pipe if (pipe(pipefd) == -1) { tio_error_printf("Failed to create pipe"); exit(EXIT_FAILURE); } // Signal that input pipe is ready pthread_mutex_unlock(&mutex_input_ready); // Input loop for stdin while (true) { /* Input from stdin ready */ byte_count = read(STDIN_FILENO, input_buffer, BUFSIZ); if (byte_count < 0) { /* No error actually occurred */ if (errno == EINTR) { continue; } tio_warning_printf("Could not read from stdin (%s)", strerror(errno)); } else if (byte_count == 0) { // Close write end to signal EOF in read end close(pipefd[1]); pthread_exit(0); } if (interactive_mode) { static char previous_char = 0; char input_char; // Process quit and flush key command for (int i = 0; i 0) { bytes_written = write(pipefd[1], input_buffer, byte_count); if (bytes_written < 0) { tio_warning_printf("Could not write to pipe (%s)", strerror(errno)); break; } byte_count -= bytes_written; } } pthread_exit(0); } void tty_input_thread_create(void) { pthread_mutex_lock(&mutex_input_ready); if (pthread_create(&thread, NULL, tty_stdin_input_thread, NULL) != 0) { tio_error_printf("pthread_create() error"); exit(1); } } void tty_input_thread_wait_ready(void) { pthread_mutex_lock(&mutex_input_ready); } static void handle_hex_prompt(char c) { hex_chars[hex_char_index++] = c; printf("%c", c); print_tainted_set(); if (hex_char_index == 2) { usleep(100*1000); if (option.local_echo == false) { printf("\b \b"); printf("\b \b"); } else { printf(" "); } unsigned char hex_value = char_to_nibble(hex_chars[0]) << 4 | (char_to_nibble(hex_chars[1]) & 0x0F); hex_char_index = 0; ssize_t status = tty_write(device_fd, &hex_value, 1); if (status < 0) { tio_warning_printf("Could not write to tty device"); } } } static const char *tty_line_name(int mask) { switch (mask) { case TIOCM_DTR: return "DTR"; case TIOCM_RTS: return "RTS"; case TIOCM_CTS: return "CTS"; case TIOCM_DSR: return "DSR"; case TIOCM_CD: return "CD"; case TIOCM_RI: return "RI"; default: return NULL; } } void tty_line_set(int fd, tty_line_config_t line_config[]) { static int line_state; int i = 0; if (ioctl(fd, TIOCMGET, &line_state) < 0) { tio_warning_printf("Could not get line state (%s)", strerror(errno)); return; } for (i=0; i<6; i++) { if (line_config[i].reserved) { if (line_config[i].value == 0) { // Low line_state |= line_config[i].mask; tio_printf("Setting %s to LOW", tty_line_name(line_config[i].mask)); } else if (line_config[i].value == 1) { // High line_state &= ~line_config[i].mask; tio_printf("Setting %s to HIGH", tty_line_name(line_config[i].mask)); } else if (line_config[i].value == 2) { // Toggle line_state ^= line_config[i].mask; if (line_state & line_config[i].mask) { tio_printf("Setting %s to LOW", tty_line_name(line_config[i].mask)); } else { tio_printf("Setting %s to HIGH", tty_line_name(line_config[i].mask)); } } } } if (ioctl(fd, TIOCMSET, &line_state) < 0) { tio_warning_printf("Could not set line state (%s)", strerror(errno)); } } void tty_line_toggle(int fd, int mask) { int line_state; if (ioctl(fd, TIOCMGET, &line_state) < 0) { tio_warning_printf("Could not get line state (%s)", strerror(errno)); return; } if (line_state & mask) { line_state &= ~mask; tio_printf("Setting %s to HIGH", tty_line_name(mask)); } else { line_state |= mask; tio_printf("Setting %s to LOW", tty_line_name(mask)); } if (ioctl(fd, TIOCMSET, &line_state) < 0) { tio_warning_printf("Could not set line state (%s)", strerror(errno)); } } static void tty_line_pulse(int fd, int mask, unsigned int duration) { tty_line_toggle(fd, mask); if (duration > 0) { tio_printf("Waiting %d ms", duration); delay(duration); } tty_line_toggle(fd, mask); } static void tty_line_poke(int fd, int mask, tty_line_mode_t mode, unsigned int duration) { switch (mode) { case LINE_TOGGLE: tty_line_toggle(fd, mask); break; case LINE_PULSE: tty_line_pulse(fd, mask, duration); break; } } static int tio_subcmd_readln(const char *title_prompt) { if (title_prompt && (title_prompt[0] != '\0')) { tio_printf_raw("%s\r\n", title_prompt); } readline_prompt_for_input(subcmd_readline_ctx); /* Read line with line edit and history. */ char c; while (true) { if (read(pipefd[0], &c, 1) > 0) { readline_input(subcmd_readline_ctx, c); if (c == '\r') break; } } strncpy(line, readline_get(subcmd_readline_ctx), PATH_MAX - 1); line[PATH_MAX - 1] = 0; return strlen(line); } void tty_output_mode_set(output_mode_t mode) { switch (mode) { case OUTPUT_MODE_NORMAL: printchar = print_normal; break; case OUTPUT_MODE_HEX: printchar = print_hex; break; case OUTPUT_MODE_END: break; } } static void mappings_print(void) { // clang-format off if (option.map_i_cr_nl || option.map_ign_cr || option.map_i_ff_escc || option.map_i_nl_cr || option.map_i_nl_crnl || option.map_i_cr_crnl || option.map_o_cr_nl || option.map_o_del_bs || option.map_o_nl_crnl || option.map_o_ltu || option.map_o_nulbrk || option.map_i_msb2lsb || option.map_o_ign_cr) { tio_printf(" Mappings:%s%s%s%s%s%s%s%s%s%s%s%s%s", option.map_i_cr_nl ? " ICRNL" : "", option.map_ign_cr ? " IGNCR" : "", option.map_i_ff_escc ? " IFFESCC" : "", option.map_i_nl_cr ? " INLCR" : "", option.map_i_nl_crnl ? " INLCRNL" : "", option.map_i_cr_crnl ? " ICRCRNL" : "", option.map_i_msb2lsb ? " IMSB2LSB" : "", option.map_o_cr_nl ? " OCRNL" : "", option.map_o_del_bs ? " ODELBS" : "", option.map_o_nl_crnl ? " ONLCRNL" : "", option.map_o_ltu ? " OLTU" : "", option.map_o_nulbrk ? " ONULBRK" : "", option.map_o_ign_cr ? " OIGNCR" : ""); } else { tio_printf(" Mappings: none"); } // clang-format on } static void handle_script_repl(void) { bool local_echo_bkup = option.local_echo; int line_len; int mline_len = 0; option.local_echo = true; tio_printf("Enter Lua REPL mode (@exit to exit)"); strcpy(mline, ""); while (true) { tio_subcmd_readln(""); if (strcmp(line, "@exit") == 0) break; line_len = strlen(line); if (line_len > 0) { if (mline_len + line_len + 1 > MLINE_MAX) { tio_printf("Too long lines. The size should be lesser then %d bytes", MLINE_MAX); strcpy(mline, ""); mline_len = 0; continue; } strcat(&mline[mline_len], line); mline_len += line_len; if (mline_len > 0 && mline[mline_len - 1] == '\\') { mline[mline_len - 1] = '\n'; continue; } } script_do_line(mline); strcpy(mline, ""); mline_len = 0; } option.local_echo = local_echo_bkup; } void handle_command_sequence(char input_char, char *output_char, bool *forward) { char unused_char; bool unused_bool; int line_state; static tty_line_mode_t line_mode; static sub_command_t sub_command = SUBCOMMAND_NONE; static char previous_char = 0; /* Ignore unused arguments */ if (output_char == NULL) { output_char = &unused_char; } if (forward == NULL) { forward = &unused_bool; } // Handle sub commands if (sub_command) { *forward = false; switch (sub_command) { case SUBCOMMAND_NONE: break; case SUBCOMMAND_LINE_TOGGLE: case SUBCOMMAND_LINE_PULSE: switch (input_char) { case KEY_0: tty_line_poke(device_fd, TIOCM_DTR, line_mode, option.dtr_pulse_duration); break; case KEY_1: tty_line_poke(device_fd, TIOCM_RTS, line_mode, option.rts_pulse_duration); break; case KEY_2: tty_line_poke(device_fd, TIOCM_CTS, line_mode, option.cts_pulse_duration); break; case KEY_3: tty_line_poke(device_fd, TIOCM_DSR, line_mode, option.dsr_pulse_duration); break; case KEY_4: tty_line_poke(device_fd, TIOCM_CD, line_mode, option.dcd_pulse_duration); break; case KEY_5: tty_line_poke(device_fd, TIOCM_RI, line_mode, option.ri_pulse_duration); break; default: tio_error_print("Invalid line number"); break; } break; case SUBCOMMAND_XMODEM: state_t state_orig = state; state = STATE_XYMODEM; tty_tcsetattr(device_fd); switch (input_char) { case KEY_0: tio_printf("Send file with XMODEM-1K"); if (tio_subcmd_readln("Enter file name: ")) { int ret; tio_printf("Sending file '%s' ", line); tio_printf("Press any key to abort transfer"); ret = xymodem_send(device_fd, line, XMODEM_1K); tio_printf("%s", ret < 0 ? "Aborted" : "Done"); } break; case KEY_1: tio_printf("Send file with XMODEM-CRC"); if (tio_subcmd_readln("Enter file name: ")) { int ret; tio_printf("Sending file '%s' ", line); tio_printf("Press any key to abort transfer"); ret = xymodem_send(device_fd, line, XMODEM_CRC); tio_printf("%s", ret < 0 ? "Aborted" : "Done"); } break; case KEY_2: tio_printf("Receive file with XMODEM-CRC"); if (tio_subcmd_readln("Enter file name: ")) { int ret; tio_printf("Ready to receiving file '%s' ", line); tio_printf("Press any key to abort transfer"); ret = xymodem_receive(device_fd, line, XMODEM_CRC); tio_printf("%s", ret < 0 ? "Aborted" : "Done"); } break; case KEY_3: tio_printf("Send file with XMODEM-SUM"); if (tio_subcmd_readln("Enter file name: ")) { int ret; tio_printf("Sending file '%s' ", line); tio_printf("Press any key to abort transfer"); ret = xymodem_send(device_fd, line, XMODEM_SUM); tio_printf("%s", ret < 0 ? "Aborted" : "Done"); } break; case KEY_4: tio_printf("Receive file with XMODEM-SUM"); if (tio_subcmd_readln("Enter file name: ")) { int ret; tio_printf("Ready to receiving file '%s' ", line); tio_printf("Press any key to abort transfer"); ret = xymodem_receive(device_fd, line, XMODEM_SUM); tio_printf("%s", ret < 0 ? "Aborted" : "Done"); } break; default: tio_error_print("Invalid protocol option"); break; } state = state_orig; tty_tcsetattr(device_fd); break; case SUBCOMMAND_MAP: switch (input_char) { case KEY_0: option.map_i_cr_nl = !option.map_i_cr_nl; tty_reconfigure(); tio_printf("ICRNL is %s", option.map_i_cr_nl ? "set" : "unset"); break; case KEY_1: option.map_ign_cr = !option.map_ign_cr; tty_reconfigure(); tio_printf("IGNCR is %s", option.map_ign_cr ? "set" : "unset"); break; case KEY_2: option.map_i_ff_escc = !option.map_i_ff_escc; tio_printf("IFFESCC is %s", option.map_i_ff_escc ? "set" : "unset"); break; case KEY_3: option.map_i_nl_cr = !option.map_i_nl_cr; tty_reconfigure(); tio_printf("INLCR is %s", option.map_i_nl_cr ? "set" : "unset"); break; case KEY_4: option.map_i_nl_crnl = !option.map_i_nl_crnl; tio_printf("INLCRNL is %s", option.map_i_nl_crnl ? "set" : "unset"); break; case KEY_5: option.map_i_cr_crnl = !option.map_i_cr_crnl; tio_printf("ICRCRNL is %s", option.map_i_cr_crnl ? "set" : "unset"); break; case KEY_6: option.map_i_msb2lsb = !option.map_i_msb2lsb; tio_printf("IMSB2LSB is %s", option.map_i_msb2lsb ? "set" : "unset"); break; case KEY_7: option.map_o_cr_nl = !option.map_o_cr_nl; tio_printf("OCRNL is %s", option.map_o_cr_nl ? "set" : "unset"); break; case KEY_8: option.map_o_del_bs = !option.map_o_del_bs; tio_printf("ODELBS is %s", option.map_o_del_bs ? "set" : "unset"); break; case KEY_9: option.map_o_nl_crnl = !option.map_o_nl_crnl; tio_printf("ONLCRNL is %s", option.map_o_nl_crnl ? "set" : "unset"); break; case KEY_A: option.map_o_ltu = !option.map_o_ltu; tio_printf("OLTU is %s", option.map_o_ltu ? "set" : "unset"); break; case KEY_B: option.map_o_nulbrk = !option.map_o_nulbrk; tio_printf("ONULBRK is %s", option.map_o_nulbrk ? "set" : "unset"); break; case KEY_C: option.map_o_ign_cr = !option.map_o_ign_cr; tio_printf("OIGNCR is %s", option.map_o_ign_cr ? "set" : "unset"); break; default: tio_error_print("Invalid input"); break; } break; } sub_command = SUBCOMMAND_NONE; return; } /* Handle escape key commands */ if (option.prefix_enabled && previous_char == option.prefix_code) { /* Do not forward input char to output by default */ *forward = false; /* Handle special double prefix key input case */ if (input_char == option.prefix_code) { /* Forward prefix character to tty */ *output_char = option.prefix_code; *forward = true; previous_char = 0; return; } // Handle user keymapped commands for (int idx = 0; idx < KEYMAP_MAX; idx++) { if ((input_char >= 0x00) && (input_char <= 0x1f)) { int ctrl_key_ch = ctrl_key_char(input_char); if ((ctrl_key_ch >= 0) && (strncmp("ctrl-", keymaps[idx].key, 5) == 0) && (keymaps[idx].key[5] == ctrl_key_ch)) { script_run(keymaps[idx].func); goto handle_commands_end; } } else if (input_char == keymaps[idx].key[0] && keymaps[idx].key[1] == '\0') { script_run(keymaps[idx].func); goto handle_commands_end; } } // Handle commands switch (input_char) { case KEY_QUESTION: tio_printf("Key commands:"); tio_printf(" ctrl-%c ? List available key commands", option.prefix_key); tio_printf(" ctrl-%c b Send break", option.prefix_key); tio_printf(" ctrl-%c c Show configuration", option.prefix_key); tio_printf(" ctrl-%c e Toggle local echo mode", option.prefix_key); tio_printf(" ctrl-%c f Toggle log to file", option.prefix_key); tio_printf(" ctrl-%c F Flush data I/O buffers", option.prefix_key); tio_printf(" ctrl-%c g Toggle serial port line", option.prefix_key); tio_printf(" ctrl-%c i Toggle input mode", option.prefix_key); tio_printf(" ctrl-%c j Toggle raw mode for non-interactive use", option.prefix_key); tio_printf(" ctrl-%c J Toggle raw mode for interactive use", option.prefix_key); tio_printf(" ctrl-%c l Clear screen", option.prefix_key); tio_printf(" ctrl-%c L Show line states", option.prefix_key); tio_printf(" ctrl-%c m Change mapping of characters on input or output", option.prefix_key); tio_printf(" ctrl-%c o Toggle output mode", option.prefix_key); tio_printf(" ctrl-%c p Pulse serial port line", option.prefix_key); tio_printf(" ctrl-%c q Quit", option.prefix_key); tio_printf(" ctrl-%c r Run script", option.prefix_key); tio_printf(" ctrl-%c R Execute shell command with I/O redirected to device", option.prefix_key); tio_printf(" ctrl-%c s Show statistics", option.prefix_key); tio_printf(" ctrl-%c t Toggle line timestamp mode", option.prefix_key); tio_printf(" ctrl-%c v Show version", option.prefix_key); tio_printf(" ctrl-%c x Send/Receive file via Xmodem", option.prefix_key); tio_printf(" ctrl-%c y Send file via Ymodem", option.prefix_key); tio_printf(" ctrl-%c ctrl-%c Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key); keymaps_print("User key commands:", 1); break; case KEY_SHIFT_L: if (ioctl(device_fd, TIOCMGET, &line_state) < 0) { tio_warning_printf("Could not get line state (%s)", strerror(errno)); break; } tio_printf("Line states:"); tio_printf(" DTR: %s", (line_state & TIOCM_DTR) ? "LOW" : "HIGH"); tio_printf(" RTS: %s", (line_state & TIOCM_RTS) ? "LOW" : "HIGH"); tio_printf(" CTS: %s", (line_state & TIOCM_CTS) ? "LOW" : "HIGH"); tio_printf(" DSR: %s", (line_state & TIOCM_DSR) ? "LOW" : "HIGH"); tio_printf(" DCD: %s", (line_state & TIOCM_CD) ? "LOW" : "HIGH"); tio_printf(" RI : %s", (line_state & TIOCM_RI) ? "LOW" : "HIGH"); break; case KEY_F: if (option.log) { log_close(); option.log = false; } else { if (log_open(option.log_filename) == 0) { option.log = true; } } tio_printf("Switched log to file %s", option.log ? "on" : "off"); break; case KEY_SHIFT_F: break; case KEY_G: tio_printf("Please enter which serial line number to toggle:"); tio_printf("(0) DTR"); tio_printf("(1) RTS"); tio_printf("(2) CTS"); tio_printf("(3) DSR"); tio_printf("(4) DCD"); tio_printf("(5) RI"); line_mode = LINE_TOGGLE; // Process next input character as sub command sub_command = SUBCOMMAND_LINE_TOGGLE; break; case KEY_P: tio_printf("Please enter which serial line number to pulse:"); tio_printf("(0) DTR"); tio_printf("(1) RTS"); tio_printf("(2) CTS"); tio_printf("(3) DSR"); tio_printf("(4) DCD"); tio_printf("(5) RI"); line_mode = LINE_PULSE; // Process next input character as sub command sub_command = SUBCOMMAND_LINE_PULSE; break; case KEY_B: tcsendbreak(device_fd, 0); break; case KEY_C: tio_printf("Configuration:"); config_file_print(); options_print(); if (option.rs485) { rs485_print_config(); } mappings_print(); keymaps_print(" Keymaps:", 4); break; case KEY_E: option.local_echo = !option.local_echo; tio_printf("Switched local echo %s", option.local_echo ? "on" : "off"); break; case KEY_I: option.input_mode += 1; switch (option.input_mode) { case INPUT_MODE_NORMAL: break; case INPUT_MODE_HEX: option.input_mode = INPUT_MODE_HEX; tio_printf("Switched input mode to hex"); break; case INPUT_MODE_LINE: option.input_mode = INPUT_MODE_LINE; tio_printf("Switched input mode to line"); break; case INPUT_MODE_END: option.input_mode = INPUT_MODE_NORMAL; tio_printf("Switched input mode to normal"); break; } break; case KEY_O: option.output_mode += 1; switch (option.output_mode) { case OUTPUT_MODE_NORMAL: break; case OUTPUT_MODE_HEX: tty_output_mode_set(OUTPUT_MODE_HEX); tio_printf("Switched output mode to hex"); break; case OUTPUT_MODE_END: option.output_mode = OUTPUT_MODE_NORMAL; tty_output_mode_set(OUTPUT_MODE_NORMAL); tio_printf("Switched output mode to normal"); break; } break; case KEY_J: option.raw += 1; switch (option.raw) { case RAW_ON_DELAY: tio_printf("Turn on raw mode for non-interactive use"); break; case RAW_ON_NODELAY: tio_printf("Turn on raw-nodelay mode for non-interactive use"); break; case RAW_OFF: default: option.raw = RAW_OFF; tio_printf("Turn off raw mode for non-interactive use"); break; } if (state != STATE_INTERACTIVE) { tty_tcsetattr(device_fd); } break; case KEY_SHIFT_J: option.raw_interactive += 1; switch (option.raw_interactive) { case RAW_ON_DELAY: tio_printf("Turn on raw mode for interactive use"); break; case RAW_ON_NODELAY: tio_printf("Turn on raw-nodelay mode for interactive use"); break; case RAW_OFF: default: option.raw_interactive = RAW_OFF; tio_printf("Turn off raw mode for interactive use"); break; } if (state == STATE_INTERACTIVE) { tty_tcsetattr(device_fd); } break; case KEY_K: /* Set keymap */ tio_subcmd_readln("Enter keymap @=|!