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
* Support for delayed output per character
* Support for delayed output per line
* Hexadecimal mode
* Log to file
* Autogeneration of log filename
* Switchable independent input and output mode (normal vs hex)
* Log to file with automatic or manual naming of log file
* Configuration file support
* Activate sub-configurations by name or pattern
* 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
.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
.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)
.IP "\fBctrl-t g"
Toggle serial port line
.IP "\fBctrl-t h"
Toggle hexadecimal mode
.IP "\fBctrl-t i"
Toggle input mode
.IP "\fBctrl-t l"
Clear screen
.IP "\fBctrl-t L"
Show line states (DTR, RTS, CTS, DSR, DCD, RI)
.IP "\fBctrl-t m"
Toggle MSB to LSB bit order
.IP "\fBctrl-t o"
Toggle output mode
.IP "\fBctrl-t p"
Pulse serial port line
.IP "\fBctrl-t q"
@ -360,15 +373,6 @@ Send a file using the YMODEM protocol (prompts for file name)
.IP "\fBctrl-t ctrl-t"
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"
.PP
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
.IP "\fBcolor"
Colorize tio text using ANSI color code ranging from 0 to 255
.IP "\fBhexadecimal"
Enable hexadecimal mode
.IP "\fBinput-mode"
Set input mode.
.IP "\fBoutput-mode"
Set output mode.
.IP "\fBsocket"
Set socket to redirect I/O to
.IP "\fBprefix-ctrl-key"

View file

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

View file

@ -59,6 +59,8 @@ enum opt_t
OPT_SCRIPT,
OPT_SCRIPT_FILE,
OPT_SCRIPT_RUN,
OPT_INPUT_MODE,
OPT_OUTPUT_MODE,
};
/* Default options */
@ -89,7 +91,8 @@ struct option_t option =
.socket = NULL,
.map = "",
.color = 256, // Bold
.hex_mode = false,
.input_mode = INPUT_MODE_NORMAL,
.output_mode = OUTPUT_MODE_NORMAL,
.prefix_code = 20, // ctrl-t
.prefix_key = 't',
.prefix_enabled = true,
@ -137,7 +140,8 @@ void print_help(char *argv[])
printf(" -m, --map <flags> Map characters\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(" -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(" --response-timeout <ms> Response timeout (default: 100)\n");
printf(" --rs-485 Enable RS-485 mode\n");
@ -215,6 +219,70 @@ void line_pulse_duration_option_parse(const char *arg)
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)
{
if (strcmp("once", arg) == 0)
@ -255,7 +323,8 @@ void options_print()
option.dsr_pulse_duration,
option.dcd_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)
tio_printf(" Map flags: %s", option.map);
if (option.log)
@ -304,7 +373,8 @@ void options_parse(int argc, char *argv[])
{"socket", required_argument, 0, 'S' },
{"map", required_argument, 0, 'm' },
{"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-timeout", required_argument, 0, OPT_RESPONSE_TIMEOUT },
{"rs-485", no_argument, 0, OPT_RS485 },
@ -453,8 +523,12 @@ void options_parse(int argc, char *argv[])
}
break;
case 'x':
option.hex_mode = true;
case OPT_INPUT_MODE:
option.input_mode = input_mode_option_parse(optarg);
break;
case OPT_OUTPUT_MODE:
option.output_mode = output_mode_option_parse(optarg);
break;
case 'r':

View file

@ -30,6 +30,20 @@
#include "timestamp.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 */
struct option_t
{
@ -58,7 +72,8 @@ struct option_t
const char *map;
const char *socket;
int color;
bool hex_mode;
input_mode_t input_mode;
output_mode_t output_mode;
unsigned char prefix_code;
unsigned char prefix_key;
bool prefix_enabled;
@ -84,3 +99,6 @@ void options_parse_final(int argc, char *argv[]);
void line_pulse_duration_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("");
}
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_init_ansi_formatting(void);
void tio_printf_array(const char *array);
void print_tainted_set(void);

118
src/tty.c
View file

@ -98,10 +98,11 @@
#define KEY_F 0x66
#define KEY_SHIFT_F 0x46
#define KEY_G 0x67
#define KEY_H 0x68
#define KEY_I 0x69
#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
@ -180,11 +181,15 @@ static void optional_local_echo(char c)
{
return;
}
print(c);
if (option.log)
{
log_putc(c);
}
print_tainted_set();
}
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);
}
static void output_hex(char c)
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;
optional_local_echo(hex_value);
ssize_t status = tty_write(fd, &hex_value, 1);
if (status < 0)
{
@ -629,6 +640,23 @@ static int tio_readln(void)
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)
{
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 Flush data I/O buffers", 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 Show line states", 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 q Quit", 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");
break;
case KEY_H:
/* Toggle hexadecimal printing mode */
if (!option.hex_mode)
case KEY_I:
option.input_mode += 1;
switch (option.input_mode)
{
print = print_hex;
option.hex_mode = true;
tio_printf("Switched to hexadecimal mode");
case INPUT_MODE_NORMAL:
break;
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;
option.hex_mode = false;
tio_printf("Switched to normal mode");
case OUTPUT_MODE_NORMAL:
break;
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;
@ -1379,9 +1431,12 @@ void forward_to_tty(int fd, char output_char)
}
else
{
if (option.hex_mode)
switch (option.output_mode)
{
output_hex(output_char);
case OUTPUT_MODE_NORMAL:
if (option.input_mode == INPUT_MODE_HEX)
{
handle_hex_prompt(output_char);
}
else
{
@ -1403,6 +1458,22 @@ void forward_to_tty(int fd, char output_char)
/* Update transmit statistics */
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 */
if (option.hex_mode)
{
print = print_hex;
}
else
{
print = print_normal;
}
tty_output_mode_set(option.output_mode);
/* Save current port settings */
if (tcgetattr(fd, &tio_old) < 0)
@ -1577,7 +1641,7 @@ int tty_connect(void)
input_char = input_buffer[i];
/* 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();
if (now)
@ -1697,7 +1761,7 @@ int tty_connect(void)
/* Handle commands */
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))
{