Add independent input and output mode

Replaces -x, --hexadecimal option with --intput-mode and --output-mode
so it is possible to select hex or normal mode for both input and output
independently.

To obtain same behaviour as -x, --hexadecimal use the following
configuration:

input-mode = hex
output-mode = hex
This commit is contained in:
Martin Lund 2024-04-10 14:39:42 +02:00
parent fd6a246908
commit 2fff4d36d0
9 changed files with 254 additions and 78 deletions

View file

@ -46,9 +46,8 @@ when used in combination with [tmux](https://tmux.github.io).
* Line timestamps * Line timestamps
* Support for delayed output per character * Support for delayed output per character
* Support for delayed output per line * Support for delayed output per line
* Hexadecimal mode * Switchable independent input and output mode (normal vs hex)
* Log to file * Log to file with automatic or manual naming of log file
* Autogeneration of log filename
* Configuration file support * Configuration file support
* Activate sub-configurations by name or pattern * Activate sub-configurations by name or pattern
* Redirect I/O to UNIX socket or IPv4/v6 network socket for scripting or TTY sharing * Redirect I/O to UNIX socket or IPv4/v6 network socket for scripting or TTY sharing

View file

@ -191,9 +191,20 @@ If defining more than one flag, the flags must be comma separated.
.RE .RE
.TP .TP
.BR \-x ", " \-\-hexadecimal .BR " \-\-input\-mode " \fInormal|hex
Enable hexadecimal mode. Set input mode. In hex input mode bytes can be sent 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.
Default value is "normal".
.TP
.BR " \-\-output\-mode " \fInormal|hex
Set output mode. In hex mode each incoming byte is printed out as a hex value.
Default value is "normal".
.TP .TP
.BR \-c ", " "\-\-color " \fI0..255|bold|none|list .BR \-c ", " "\-\-color " \fI0..255|bold|none|list
@ -329,14 +340,16 @@ Toggle log to file
Flush data I/O buffers (discard data written but not transmitted and data received but not read) Flush data I/O buffers (discard data written but not transmitted and data received but not read)
.IP "\fBctrl-t g" .IP "\fBctrl-t g"
Toggle serial port line Toggle serial port line
.IP "\fBctrl-t h" .IP "\fBctrl-t i"
Toggle hexadecimal mode Toggle input mode
.IP "\fBctrl-t l" .IP "\fBctrl-t l"
Clear screen Clear screen
.IP "\fBctrl-t L" .IP "\fBctrl-t L"
Show line states (DTR, RTS, CTS, DSR, DCD, RI) Show line states (DTR, RTS, CTS, DSR, DCD, RI)
.IP "\fBctrl-t m" .IP "\fBctrl-t m"
Toggle MSB to LSB bit order Toggle MSB to LSB bit order
.IP "\fBctrl-t o"
Toggle output mode
.IP "\fBctrl-t p" .IP "\fBctrl-t p"
Pulse serial port line Pulse serial port line
.IP "\fBctrl-t q" .IP "\fBctrl-t q"
@ -360,15 +373,6 @@ Send a file using the YMODEM protocol (prompts for file name)
.IP "\fBctrl-t ctrl-t" .IP "\fBctrl-t ctrl-t"
Send ctrl-t character Send ctrl-t character
.SH "HEXADECIMAL MODE"
.PP
In hexadecimal mode each incoming byte is printed out as a hexadecimal value.
.PP
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 "SCRIPT API" .SH "SCRIPT API"
.PP .PP
Tio suppots Lua scripting for manipulating the tty device. In addition to the Tio suppots Lua scripting for manipulating the tty device. In addition to the
@ -470,8 +474,10 @@ Set timestamp format
Map characters on input or output Map characters on input or output
.IP "\fBcolor" .IP "\fBcolor"
Colorize tio text using ANSI color code ranging from 0 to 255 Colorize tio text using ANSI color code ranging from 0 to 255
.IP "\fBhexadecimal" .IP "\fBinput-mode"
Enable hexadecimal mode Set input mode.
.IP "\fBoutput-mode"
Set output mode.
.IP "\fBsocket" .IP "\fBsocket"
Set socket to redirect I/O to Set socket to redirect I/O to
.IP "\fBprefix-ctrl-key" .IP "\fBprefix-ctrl-key"

View file

@ -31,7 +31,8 @@ _tio()
-L --list-devices \ -L --list-devices \
-c --color \ -c --color \
-S --socket \ -S --socket \
-x --hexadecimal \ --input-mode \
--output-mode \
-r --response-wait \ -r --response-wait \
--response-timeout \ --response-timeout \
--rs-485 \ --rs-485 \
@ -131,8 +132,12 @@ _tio()
COMPREPLY=( $(compgen -W "unix: inet: inet6:" -- ${cur}) ) COMPREPLY=( $(compgen -W "unix: inet: inet6:" -- ${cur}) )
return 0 return 0
;; ;;
-x | --hexadecimal) --input-mode)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "normal hex" -- ${cur}) )
return 0
;;
--output-mode)
COMPREPLY=( $(compgen -W "normal hex" -- ${cur}) )
return 0 return 0
;; ;;
-r | --response-wait) -r | --response-wait)

View file

@ -213,9 +213,13 @@ static int data_handler(void *user, const char *section, const char *name,
{ {
option.local_echo = read_boolean(value, name); option.local_echo = read_boolean(value, name);
} }
else if (!strcmp(name, "hexadecimal")) else if (!strcmp(name, "input-mode"))
{ {
option.hex_mode = read_boolean(value, name); option.input_mode = input_mode_option_parse(value);
}
else if (!strcmp(name, "output-mode"))
{
option.output_mode = output_mode_option_parse(value);
} }
else if (!strcmp(name, "timestamp")) else if (!strcmp(name, "timestamp"))
{ {

View file

@ -59,6 +59,8 @@ enum opt_t
OPT_SCRIPT, OPT_SCRIPT,
OPT_SCRIPT_FILE, OPT_SCRIPT_FILE,
OPT_SCRIPT_RUN, OPT_SCRIPT_RUN,
OPT_INPUT_MODE,
OPT_OUTPUT_MODE,
}; };
/* Default options */ /* Default options */
@ -89,7 +91,8 @@ struct option_t option =
.socket = NULL, .socket = NULL,
.map = "", .map = "",
.color = 256, // Bold .color = 256, // Bold
.hex_mode = false, .input_mode = INPUT_MODE_NORMAL,
.output_mode = OUTPUT_MODE_NORMAL,
.prefix_code = 20, // ctrl-t .prefix_code = 20, // ctrl-t
.prefix_key = 't', .prefix_key = 't',
.prefix_enabled = true, .prefix_enabled = true,
@ -137,7 +140,8 @@ void print_help(char *argv[])
printf(" -m, --map <flags> Map characters\n"); printf(" -m, --map <flags> Map characters\n");
printf(" -c, --color 0..255|bold|none|list Colorize tio text (default: bold)\n"); printf(" -c, --color 0..255|bold|none|list Colorize tio text (default: bold)\n");
printf(" -S, --socket <socket> Redirect I/O to socket\n"); printf(" -S, --socket <socket> Redirect I/O to socket\n");
printf(" -x, --hexadecimal Enable hexadecimal mode\n"); printf(" --input-mode normal|hex Select input mode (default: normal)\n");
printf(" --output-mode normal|hex Select output mode (default: normal)\n");
printf(" -r, --response-wait Wait for line response then quit\n"); printf(" -r, --response-wait Wait for line response then quit\n");
printf(" --response-timeout <ms> Response timeout (default: 100)\n"); printf(" --response-timeout <ms> Response timeout (default: 100)\n");
printf(" --rs-485 Enable RS-485 mode\n"); printf(" --rs-485 Enable RS-485 mode\n");
@ -215,6 +219,70 @@ void line_pulse_duration_option_parse(const char *arg)
free(buffer); free(buffer);
} }
input_mode_t input_mode_option_parse(const char *arg)
{
if (strcmp("normal", arg) == 0)
{
return INPUT_MODE_NORMAL;
}
else if (strcmp("hex", arg) == 0)
{
return INPUT_MODE_HEX;
}
else
{
tio_error_printf("Invalid input mode option");
exit(EXIT_FAILURE);
}
}
output_mode_t output_mode_option_parse(const char *arg)
{
if (strcmp("normal", arg) == 0)
{
return OUTPUT_MODE_NORMAL;
}
else if (strcmp("hex", arg) == 0)
{
return OUTPUT_MODE_HEX;
}
else
{
tio_error_printf("Invalid output mode option");
exit(EXIT_FAILURE);
}
}
const char *input_mode_by_string(input_mode_t mode)
{
switch (mode)
{
case INPUT_MODE_NORMAL:
return "normal";
case INPUT_MODE_HEX:
return "hex";
case INPUT_MODE_END:
break;
}
return NULL;
}
const char *output_mode_by_string(output_mode_t mode)
{
switch (mode)
{
case OUTPUT_MODE_NORMAL:
return "normal";
case OUTPUT_MODE_HEX:
return "hex";
case OUTPUT_MODE_END:
break;
}
return NULL;
}
enum script_run_t script_run_option_parse(const char *arg) enum script_run_t script_run_option_parse(const char *arg)
{ {
if (strcmp("once", arg) == 0) if (strcmp("once", arg) == 0)
@ -255,7 +323,8 @@ void options_print()
option.dsr_pulse_duration, option.dsr_pulse_duration,
option.dcd_pulse_duration, option.dcd_pulse_duration,
option.ri_pulse_duration); option.ri_pulse_duration);
tio_printf(" Hexadecimal mode: %s", option.hex_mode ? "enabled" : "disabled"); tio_printf(" Input mode: %s", input_mode_by_string(option.input_mode));
tio_printf(" Output mode: %s", output_mode_by_string(option.output_mode));
if (option.map[0] != 0) if (option.map[0] != 0)
tio_printf(" Map flags: %s", option.map); tio_printf(" Map flags: %s", option.map);
if (option.log) if (option.log)
@ -304,7 +373,8 @@ void options_parse(int argc, char *argv[])
{"socket", required_argument, 0, 'S' }, {"socket", required_argument, 0, 'S' },
{"map", required_argument, 0, 'm' }, {"map", required_argument, 0, 'm' },
{"color", required_argument, 0, 'c' }, {"color", required_argument, 0, 'c' },
{"hexadecimal", no_argument, 0, 'x' }, {"input-mode", required_argument, 0, OPT_INPUT_MODE },
{"output-mode", required_argument, 0, OPT_OUTPUT_MODE },
{"response-wait", no_argument, 0, 'r' }, {"response-wait", no_argument, 0, 'r' },
{"response-timeout", required_argument, 0, OPT_RESPONSE_TIMEOUT }, {"response-timeout", required_argument, 0, OPT_RESPONSE_TIMEOUT },
{"rs-485", no_argument, 0, OPT_RS485 }, {"rs-485", no_argument, 0, OPT_RS485 },
@ -453,8 +523,12 @@ void options_parse(int argc, char *argv[])
} }
break; break;
case 'x': case OPT_INPUT_MODE:
option.hex_mode = true; option.input_mode = input_mode_option_parse(optarg);
break;
case OPT_OUTPUT_MODE:
option.output_mode = output_mode_option_parse(optarg);
break; break;
case 'r': case 'r':

View file

@ -30,6 +30,20 @@
#include "timestamp.h" #include "timestamp.h"
#include "alert.h" #include "alert.h"
typedef enum
{
INPUT_MODE_NORMAL,
INPUT_MODE_HEX,
INPUT_MODE_END,
} input_mode_t;
typedef enum
{
OUTPUT_MODE_NORMAL,
OUTPUT_MODE_HEX,
OUTPUT_MODE_END,
} output_mode_t;
/* Options */ /* Options */
struct option_t struct option_t
{ {
@ -58,7 +72,8 @@ struct option_t
const char *map; const char *map;
const char *socket; const char *socket;
int color; int color;
bool hex_mode; input_mode_t input_mode;
output_mode_t output_mode;
unsigned char prefix_code; unsigned char prefix_code;
unsigned char prefix_key; unsigned char prefix_key;
bool prefix_enabled; bool prefix_enabled;
@ -84,3 +99,6 @@ void options_parse_final(int argc, char *argv[]);
void line_pulse_duration_option_parse(const char *arg); void line_pulse_duration_option_parse(const char *arg);
enum script_run_t script_run_option_parse(const char *arg); enum script_run_t script_run_option_parse(const char *arg);
input_mode_t input_mode_option_parse(const char *arg);
output_mode_t output_mode_option_parse(const char *arg);

View file

@ -73,3 +73,8 @@ void tio_printf_array(const char *array)
} }
tio_printf(""); tio_printf("");
} }
void print_tainted_set()
{
print_tainted = true;
}

View file

@ -116,3 +116,4 @@ void print_hex(char c);
void print_normal(char c); void print_normal(char c);
void print_init_ansi_formatting(void); void print_init_ansi_formatting(void);
void tio_printf_array(const char *array); void tio_printf_array(const char *array);
void print_tainted_set(void);

160
src/tty.c
View file

@ -98,10 +98,11 @@
#define KEY_F 0x66 #define KEY_F 0x66
#define KEY_SHIFT_F 0x46 #define KEY_SHIFT_F 0x46
#define KEY_G 0x67 #define KEY_G 0x67
#define KEY_H 0x68 #define KEY_I 0x69
#define KEY_L 0x6C #define KEY_L 0x6C
#define KEY_SHIFT_L 0x4C #define KEY_SHIFT_L 0x4C
#define KEY_M 0x6D #define KEY_M 0x6D
#define KEY_O 0x6F
#define KEY_P 0x70 #define KEY_P 0x70
#define KEY_Q 0x71 #define KEY_Q 0x71
#define KEY_R 0x72 #define KEY_R 0x72
@ -180,11 +181,15 @@ static void optional_local_echo(char c)
{ {
return; return;
} }
print(c); print(c);
if (option.log) if (option.log)
{ {
log_putc(c); log_putc(c);
} }
print_tainted_set();
} }
inline static bool is_valid_hex(char c) inline static bool is_valid_hex(char c)
@ -407,23 +412,29 @@ void tty_input_thread_wait_ready(void)
pthread_mutex_lock(&mutex_input_ready); pthread_mutex_lock(&mutex_input_ready);
} }
static void output_hex(char c) static void handle_hex_prompt(char c)
{ {
hex_chars[hex_char_index++] = c; hex_chars[hex_char_index++] = c;
printf("%c", c); printf("%c", c);
print_tainted_set();
if (hex_char_index == 2) if (hex_char_index == 2)
{ {
usleep(100*1000); usleep(100*1000);
printf("\b \b"); if (option.local_echo == false)
printf("\b \b"); {
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); unsigned char hex_value = char_to_nibble(hex_chars[0]) << 4 | (char_to_nibble(hex_chars[1]) & 0x0F);
hex_char_index = 0; hex_char_index = 0;
optional_local_echo(hex_value);
ssize_t status = tty_write(fd, &hex_value, 1); ssize_t status = tty_write(fd, &hex_value, 1);
if (status < 0) if (status < 0)
{ {
@ -629,6 +640,23 @@ static int tio_readln(void)
return (p - line); return (p - line);
} }
void tty_output_mode_set(output_mode_t mode)
{
switch (mode)
{
case OUTPUT_MODE_NORMAL:
print = print_normal;
break;
case OUTPUT_MODE_HEX:
print = print_hex;
break;
case OUTPUT_MODE_END:
break;
}
}
void handle_command_sequence(char input_char, char *output_char, bool *forward) void handle_command_sequence(char input_char, char *output_char, bool *forward)
{ {
char unused_char; char unused_char;
@ -709,10 +737,11 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
tio_printf(" ctrl-%c f Toggle log to file", 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 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 g Toggle serial port line", option.prefix_key);
tio_printf(" ctrl-%c h Toggle hexadecimal mode", option.prefix_key); tio_printf(" ctrl-%c i Toggle input mode", option.prefix_key);
tio_printf(" ctrl-%c l Clear screen", 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 L Show line states", option.prefix_key);
tio_printf(" ctrl-%c m Toggle MSB to LSB bit order", option.prefix_key); tio_printf(" ctrl-%c m Toggle MSB to LSB bit order", 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 p Pulse serial port line", option.prefix_key);
tio_printf(" ctrl-%c q Quit", 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 Run script", option.prefix_key);
@ -803,19 +832,42 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
tio_printf("Switched local echo %s", option.local_echo ? "on" : "off"); tio_printf("Switched local echo %s", option.local_echo ? "on" : "off");
break; break;
case KEY_H: case KEY_I:
/* Toggle hexadecimal printing mode */ option.input_mode += 1;
if (!option.hex_mode) switch (option.input_mode)
{ {
print = print_hex; case INPUT_MODE_NORMAL:
option.hex_mode = true; break;
tio_printf("Switched to hexadecimal mode");
case INPUT_MODE_HEX:
option.input_mode = INPUT_MODE_HEX;
tio_printf("Switched to hex input mode");
break;
case INPUT_MODE_END:
option.input_mode = INPUT_MODE_NORMAL;
tio_printf("Switched to normal input mode");
break;
} }
else break;
case KEY_O:
option.output_mode += 1;
switch (option.output_mode)
{ {
print = print_normal; case OUTPUT_MODE_NORMAL:
option.hex_mode = false; break;
tio_printf("Switched to normal mode");
case OUTPUT_MODE_HEX:
tty_output_mode_set(OUTPUT_MODE_HEX);
tio_printf("Switched to hex output mode");
break;
case OUTPUT_MODE_END:
option.output_mode = OUTPUT_MODE_NORMAL;
tty_output_mode_set(OUTPUT_MODE_NORMAL);
tio_printf("Switched to normal output mode");
break;
} }
break; break;
@ -1379,29 +1431,48 @@ void forward_to_tty(int fd, char output_char)
} }
else else
{ {
if (option.hex_mode) switch (option.output_mode)
{ {
output_hex(output_char); case OUTPUT_MODE_NORMAL:
} if (option.input_mode == INPUT_MODE_HEX)
else {
{ handle_hex_prompt(output_char);
/* Send output to tty device */ }
optional_local_echo(output_char); else
if ((output_char == 0) && (map_o_nulbrk)) {
{ /* Send output to tty device */
status = tcsendbreak(fd, 0); optional_local_echo(output_char);
} if ((output_char == 0) && (map_o_nulbrk))
else {
{ status = tcsendbreak(fd, 0);
status = tty_write(fd, &output_char, 1); }
} else
if (status < 0) {
{ status = tty_write(fd, &output_char, 1);
tio_warning_printf("Could not write to tty device"); }
} if (status < 0)
{
tio_warning_printf("Could not write to tty device");
}
/* Update transmit statistics */ /* Update transmit statistics */
tx_total++; tx_total++;
}
break;
case OUTPUT_MODE_HEX:
if (option.input_mode == INPUT_MODE_HEX)
{
handle_hex_prompt(output_char);
}
else
{
optional_local_echo(output_char);
}
break;
case OUTPUT_MODE_END:
break;
} }
} }
} }
@ -1460,14 +1531,7 @@ int tty_connect(void)
} }
/* Manage print output mode */ /* Manage print output mode */
if (option.hex_mode) tty_output_mode_set(option.output_mode);
{
print = print_hex;
}
else
{
print = print_normal;
}
/* Save current port settings */ /* Save current port settings */
if (tcgetattr(fd, &tio_old) < 0) if (tcgetattr(fd, &tio_old) < 0)
@ -1577,7 +1641,7 @@ int tty_connect(void)
input_char = input_buffer[i]; input_char = input_buffer[i];
/* Print timestamp on new line if enabled */ /* Print timestamp on new line if enabled */
if ((next_timestamp && input_char != '\n' && input_char != '\r') && !option.hex_mode) if ((next_timestamp && input_char != '\n' && input_char != '\r') && (option.output_mode == OUTPUT_MODE_NORMAL))
{ {
now = timestamp_current_time(); now = timestamp_current_time();
if (now) if (now)
@ -1697,7 +1761,7 @@ int tty_connect(void)
/* Handle commands */ /* Handle commands */
handle_command_sequence(input_char, &output_char, &forward); handle_command_sequence(input_char, &output_char, &forward);
if ((option.hex_mode) && (forward)) if ((option.input_mode == INPUT_MODE_HEX) && (forward))
{ {
if (!is_valid_hex(input_char)) if (!is_valid_hex(input_char))
{ {