Add user key-script mapping function (--keymap, Ctrl-t k)

User key-script mapping function:

You can specify the mappings as @<key-1>=<script-description-1>
@<key-2>=<script-description-2>... @<key-N>=<script-description-N>.

Script-description is script-filename or '!'script-commands.

After that,
When you press ctrl-t and <key-n>, tio executes <script-description-n>.
This user keymap takes precedence over the default settings (except for
ctrl-t q).

Example of startup option:
tio /dev/ttyUSB1 --keymap '@1=!print(tio.banner())
@2=!tio.write("test\r") @ctrl-a=!tio.write("ctrl-a\r")
@ctrl-j=test-script.tio'

Example of .tioconfig: (note: backslash escape needed.)
keymap = @1=!print(tio.banner()) @2=!tio.write("test\\r")
@ctrl-a=!tio.write("ctrl-a\\r") @ctrl-j=test-script.tio
This commit is contained in:
yabu76 2026-02-08 16:17:30 +09:00
parent 8722b410a7
commit 60bc7e9cfe
6 changed files with 250 additions and 0 deletions

View file

@ -250,6 +250,13 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
g_free((void *)string); g_free((void *)string);
string = NULL; string = NULL;
} }
config_get_string(key_file, group, "keymap", &string, NULL);
if (string != NULL)
{
option_parse_key_mappings(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "color", &string, NULL); config_get_string(key_file, group, "color", &string, NULL);
if (string != NULL) if (string != NULL)
{ {

View file

@ -57,6 +57,16 @@ int ctrl_key_code(unsigned char key)
return -1; return -1;
} }
int ctrl_key_char(int key_code)
{
if (key_code >= ('a' & ~0x60) && key_code <= ('z' & ~0x60))
{
return key_code | 0x60;
}
return -1;
}
bool regex_match(const char *string, const char *pattern) bool regex_match(const char *string, const char *pattern)
{ {
regex_t regex; regex_t regex;

View file

@ -26,10 +26,15 @@
#define POLL_NOWAIT (0) #define POLL_NOWAIT (0)
#define POLL_FOREVER (-1) #define POLL_FOREVER (-1)
#define TOSTRING_(x) #x
#define TOSTR(x) TOSTRING_(x)
#define UNUSED(expr) do { (void)(expr); } while (0) #define UNUSED(expr) do { (void)(expr); } while (0)
void delay(long ms); void delay(long ms);
int ctrl_key_code(unsigned char key); int ctrl_key_code(unsigned char key);
int ctrl_key_char(int key_code);
bool regex_match(const char *string, const char *pattern); bool regex_match(const char *string, const char *pattern);
unsigned long djb2_hash(const unsigned char *str); unsigned long djb2_hash(const unsigned char *str);
void *base62_encode(unsigned long num, char *output); void *base62_encode(unsigned long num, char *output);

View file

@ -63,6 +63,7 @@ enum opt_t
OPT_EXEC, OPT_EXEC,
OPT_RAW, OPT_RAW,
OPT_RAW_INTERACTIVE, OPT_RAW_INTERACTIVE,
OPT_KEYMAP,
}; };
// clang-format off // clang-format off
@ -134,9 +135,12 @@ struct option_t option =
.map_o_ign_cr = false, .map_o_ign_cr = false,
.raw = RAW_ON_DELAY, .raw = RAW_ON_DELAY,
.raw_interactive = RAW_OFF, .raw_interactive = RAW_OFF,
.keymap = NULL,
}; };
// clang-format on // clang-format on
struct keymap_t keymaps[KEYMAP_MAX] = {0};
void option_print_help(char *argv[]) void option_print_help(char *argv[])
{ {
UNUSED(argv); UNUSED(argv);
@ -174,6 +178,7 @@ void option_print_help(char *argv[])
printf(" --log-append Append to log file\n"); printf(" --log-append Append to log file\n");
printf(" --log-strip Strip control characters and escape sequences\n"); printf(" --log-strip Strip control characters and escape sequences\n");
printf(" -m, --map <flags> Map characters\n"); printf(" -m, --map <flags> Map characters\n");
printf(" --keymap <keymaps> Set key-script mappings\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(" --raw off|on|on-nodelay Select raw mode for non-interactive use (default: on)\n"); printf(" --raw off|on|on-nodelay Select raw mode for non-interactive use (default: on)\n");
@ -945,6 +950,11 @@ void options_print()
tio_printf(" Script file: %s", option.script_filename); tio_printf(" Script file: %s", option.script_filename);
tio_printf(" Script run: %s", script_run_state_to_string(option.script_run)); tio_printf(" Script run: %s", script_run_state_to_string(option.script_run));
} }
if (option.script != NULL)
{
tio_printf(" Script command: %s", option.script);
tio_printf(" Script run: %s", script_run_state_to_string(option.script_run));
}
} }
void options_parse(int argc, char *argv[]) void options_parse(int argc, char *argv[])
@ -1003,6 +1013,7 @@ void options_parse(int argc, char *argv[])
{"log-strip", no_argument, 0, OPT_LOG_STRIP }, {"log-strip", no_argument, 0, OPT_LOG_STRIP },
{"socket", required_argument, 0, 'S' }, {"socket", required_argument, 0, 'S' },
{"map", required_argument, 0, 'm' }, {"map", required_argument, 0, 'm' },
{"keymap", required_argument, 0, OPT_KEYMAP },
{"color", required_argument, 0, 'c' }, {"color", required_argument, 0, 'c' },
{"input-mode", required_argument, 0, OPT_INPUT_MODE }, {"input-mode", required_argument, 0, OPT_INPUT_MODE },
{"output-mode", required_argument, 0, OPT_OUTPUT_MODE }, {"output-mode", required_argument, 0, OPT_OUTPUT_MODE },
@ -1215,6 +1226,11 @@ void options_parse(int argc, char *argv[])
option_parse_raw(optarg, &option.raw_interactive); option_parse_raw(optarg, &option.raw_interactive);
break; break;
case OPT_KEYMAP:
option.keymap = optarg;
option_parse_key_mappings(optarg);
break;
case 'v': case 'v':
printf("tio %s\n", VERSION); printf("tio %s\n", VERSION);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -1299,3 +1315,169 @@ void options_parse_final(int argc, char *argv[])
// clang-format on // clang-format on
#endif #endif
} }
int keymap_set(char *key_str, int key_len, char *func_str, int func_len)
{
char func_str_r[KEYMAP_FUNC_STR_MAX + 1];
char *srcp;
int dst_ofs;
int key_ofs;
int empty_idx, matched_idx, idx;
bool found_empty = false;
bool found_matched = false;
bool unset_requested = false;
if (key_str[key_len] != '\0' || func_str[func_len] != '\0')
{
return -1;
}
/* key_str should not include spaces */
key_ofs = 0;
for (key_ofs = 0; key_ofs < key_len; key_ofs++)
{
if (key_str[key_ofs] == ' ')
{
tio_error_printf("Key should not include space");
return -1;
}
}
/* check disallowed key_str */
if (strcmp(key_str, "q") == 0)
{
tio_error_printf("Key %s is immutable", key_str);
return -1;
}
/* remove prefix spaces and postfix spaces from func_str */
for (srcp = func_str; *srcp != '\0'; srcp++)
{
if (*srcp != ' ')
{
break;
}
}
strncpy(func_str_r, srcp, KEYMAP_KEY_STR_MAX);
func_str_r[KEYMAP_KEY_STR_MAX] = '\0';
for (dst_ofs = strlen(func_str_r) - 1; dst_ofs >= 0; dst_ofs--)
{
if (func_str_r[dst_ofs] != ' ')
{
func_str_r[dst_ofs + 1] = '\0';
break;
}
}
if (strcmp(func_str_r, "nil") == 0 || func_str_r[0] == '\0')
{
unset_requested = true;
}
/* search for entry which key matched or is empty */
for (idx = 0; idx < KEYMAP_MAX; idx++)
{
if (found_empty == false && keymaps[idx].key[0] == '\0')
{
empty_idx = idx;
found_empty = true;
}
if (found_matched == false && strcmp(keymaps[idx].key, key_str) == 0)
{
matched_idx = idx;
found_matched = true;
}
if (found_empty && found_matched)
{
break;
}
}
/* update entry */
if (unset_requested)
{
if (found_matched)
{
keymaps[matched_idx].key[0] = '\0';
keymaps[matched_idx].func[0] = '\0';
}
}
else /* set requested */
{
if (found_matched)
{
strcpy(keymaps[matched_idx].key, key_str);
strcpy(keymaps[matched_idx].func, func_str);
}
else if (found_empty)
{
strcpy(keymaps[empty_idx].key, key_str);
strcpy(keymaps[empty_idx].func, func_str);
}
else
{
tio_error_printf("Too many keymaps", key_str);
return -1;
}
}
return 0;
}
void keymaps_print(const char *title, int indent)
{
int idx;
bool keymap_title_done = false;
for (idx = 0; idx < KEYMAP_MAX; idx++)
{
if (keymaps[idx].key[0] == '\0')
{
continue;
}
if (!keymap_title_done)
{
if (title[0] != '\0')
{
tio_printf("%s", title);
}
keymap_title_done = true;
}
tio_printf("%*sctrl-%c %s : %s", indent, " ", option.prefix_key, keymaps[idx].key, keymaps[idx].func);
}
}
void option_parse_key_mappings(const char *keymap)
{
char key_str[KEYMAP_KEY_STR_MAX + 1];
char func_str[KEYMAP_FUNC_STR_MAX + 1];
int key_len, func_len;
char *buffer;
char *cp;
if (keymap == NULL)
{
return;
}
/* Parse specified key mappings */
buffer = strdup(keymap);
cp = strchr(buffer, '@');
if (cp == NULL)
{
tio_error_print("Can't find keymap top character '@'");
goto parse_end;
}
while (sscanf(cp, "@%" TOSTR(KEYMAP_KEY_STR_MAX) "[^=]=%" TOSTR(KEYMAP_FUNC_STR_MAX) "[^@]", key_str, func_str) == 2)
{
key_len = strlen(key_str);
func_len = strlen(func_str);
keymap_set(key_str, key_len, func_str, func_len);
cp = strchr(cp + key_len + func_len + 2, '@');
if (cp == NULL)
{
break;
}
}
parse_end:
free(buffer);
}

View file

@ -105,6 +105,7 @@ struct option_t
int hex_n_value; int hex_n_value;
bool vt100; bool vt100;
char *exec; char *exec;
char *keymap;
bool map_i_nl_cr; bool map_i_nl_cr;
bool map_i_cr_nl; bool map_i_cr_nl;
bool map_ign_cr; bool map_ign_cr;
@ -120,7 +121,18 @@ struct option_t
bool map_o_ign_cr; bool map_o_ign_cr;
}; };
#define KEYMAP_MAX 32
#define KEYMAP_KEY_STR_MAX 7
#define KEYMAP_FUNC_STR_MAX 127
struct keymap_t
{
char key[KEYMAP_KEY_STR_MAX + 1];
char func[KEYMAP_FUNC_STR_MAX + 1];
};
extern struct option_t option; extern struct option_t option;
extern struct keymap_t keymaps[KEYMAP_MAX];
void options_print(); void options_print();
void options_parse(int argc, char *argv[]); void options_parse(int argc, char *argv[]);
@ -147,5 +159,8 @@ void option_parse_timestamp(const char *arg, timestamp_t *timestamp);
const char* option_timestamp_format_to_string(timestamp_t timestamp); const char* option_timestamp_format_to_string(timestamp_t timestamp);
void option_parse_mappings(const char *map); void option_parse_mappings(const char *map);
void option_parse_key_mappings(const char *keymap);
const char* option_raw_to_string(raw_t raw); const char* option_raw_to_string(raw_t raw);
void keymaps_print(const char *title, int indent);

View file

@ -119,6 +119,7 @@
#define KEY_I 0x69 #define KEY_I 0x69
#define KEY_J 0x6A #define KEY_J 0x6A
#define KEY_SHIFT_J 0x4A #define KEY_SHIFT_J 0x4A
#define KEY_K 0x6B
#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
@ -1099,6 +1100,26 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
return; 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 // Handle commands
switch (input_char) switch (input_char)
{ {
@ -1128,6 +1149,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
tio_printf(" ctrl-%c x Send/Receive file via Xmodem", 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 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); 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; break;
case KEY_SHIFT_L: case KEY_SHIFT_L:
@ -1203,6 +1225,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
rs485_print_config(); rs485_print_config();
} }
mappings_print(); mappings_print();
keymaps_print(" Keymaps:", 4);
break; break;
case KEY_E: case KEY_E:
@ -1290,6 +1313,12 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
} }
break; break;
case KEY_K:
/* Set keymap */
tio_subcmd_readln("Enter keymap @<key>=<script-file>|!<script> :");
option_parse_key_mappings(line);
break;
case KEY_L: case KEY_L:
/* Clear screen using ANSI/VT100 escape code */ /* Clear screen using ANSI/VT100 escape code */
printf("\033c"); printf("\033c");
@ -1443,6 +1472,8 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
/* Ignore unknown ctrl-t escaped keys */ /* Ignore unknown ctrl-t escaped keys */
break; break;
} }
handle_commands_end:
} }
previous_char = input_char; previous_char = input_char;