From 3b3f42053a5f11af5285392a5a072facbc16f4a9 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 16 Dec 2022 08:13:40 +0000 Subject: [PATCH] Add send-keys -K to handle keys directly as if typed (so look up in key table). GitHub issue 3361. --- arguments.c | 203 +++++++++++++++++++++++++++++----------------- cmd-find-window.c | 4 +- cmd-send-keys.c | 35 +++++--- tmux.1 | 11 ++- tmux.h | 2 +- 5 files changed, 163 insertions(+), 92 deletions(-) diff --git a/arguments.c b/arguments.c index b08582ee..47ca17ce 100644 --- a/arguments.c +++ b/arguments.c @@ -37,6 +37,10 @@ struct args_entry { u_char flag; struct args_values values; u_int count; + + int flags; +#define ARGS_ENTRY_OPTIONAL_VALUE 0x1 + RB_ENTRY(args_entry) entry; }; @@ -122,6 +126,101 @@ args_create(void) return (args); } +/* Parse a single flag. */ +static int +args_parse_flag_argument(struct args_value *values, u_int count, char **cause, + struct args *args, u_int *i, const char *string, int flag, + int optional_argument) +{ + struct args_value *argument, *new; + const char *s; + + new = xcalloc(1, sizeof *new); + if (*string != '\0') { + new->type = ARGS_STRING; + new->string = xstrdup(string); + goto out; + } + + if (*i == count) + argument = NULL; + else { + argument = &values[*i]; + if (argument->type != ARGS_STRING) { + xasprintf(cause, "-%c argument must be a string", flag); + return (-1); + } + if (argument->string[0] == '-') + argument = NULL; + } + if (argument == NULL) { + if (optional_argument) { + log_debug("%s: -%c (optional)", __func__, flag); + args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE); + return (0); /* either - or end */ + } + xasprintf(cause, "-%c expects an argument", flag); + return (-1); + } + args_copy_value(new, argument); + (*i)++; + +out: + s = args_value_as_string(new); + log_debug("%s: -%c = %s", __func__, flag, s); + args_set(args, flag, new, 0); + return (0); +} + +/* Parse flags argument. */ +static int +args_parse_flags(const struct args_parse *parse, struct args_value *values, + u_int count, char **cause, struct args *args, int *i) +{ + struct args_value *value; + u_char flag; + const char *found, *string; + int optional_argument; + + value = &values[*i]; + if (value->type != ARGS_STRING) + return (1); + + string = value->string; + log_debug("%s: next %s", __func__, string); + if (*string++ != '-' || *string == '\0') + return (1); + (*i)++; + if (string[0] == '-' && string[1] == '\0') + return (1); + + for (;;) { + flag = *string++; + if (flag == '\0') + return (0); + if (flag == '?') + return (-1); + if (!isalnum(flag)) { + xasprintf(cause, "invalid flag -%c", flag); + return (-1); + } + + found = strchr(parse->template, flag); + if (found == NULL) { + xasprintf(cause, "unknown flag -%c", flag); + return (-1); + } + if (*++found != ':') { + log_debug("%s: -%c", __func__, flag); + args_set(args, flag, NULL, 0); + continue; + } + optional_argument = (*found == ':'); + return (args_parse_flag_argument(values, count, cause, args, i, + string, flag, optional_argument)); + } +} + /* Parse arguments into a new argument set. */ struct args * args_parse(const struct args_parse *parse, struct args_value *values, @@ -131,86 +230,21 @@ args_parse(const struct args_parse *parse, struct args_value *values, u_int i; enum args_parse_type type; struct args_value *value, *new; - u_char flag; - const char *found, *string, *s; - int optional_argument; + const char *s; + int stop; if (count == 0) return (args_create()); args = args_create(); for (i = 1; i < count; /* nothing */) { - value = &values[i]; - if (value->type != ARGS_STRING) - break; - - string = value->string; - if (*string++ != '-' || *string == '\0') - break; - i++; - if (string[0] == '-' && string[1] == '\0') - break; - - for (;;) { - flag = *string++; - if (flag == '\0') - break; - if (flag == '?') { - args_free(args); - return (NULL); - } - if (!isalnum(flag)) { - xasprintf(cause, "invalid flag -%c", flag); - args_free(args); - return (NULL); - } - found = strchr(parse->template, flag); - if (found == NULL) { - xasprintf(cause, "unknown flag -%c", flag); - args_free(args); - return (NULL); - } - if (*++found != ':') { - log_debug("%s: -%c", __func__, flag); - args_set(args, flag, NULL); - continue; - } - if (*found == ':') { - optional_argument = 1; - found++; - } - new = xcalloc(1, sizeof *new); - if (*string != '\0') { - new->type = ARGS_STRING; - new->string = xstrdup(string); - } else { - if (i == count) { - if (optional_argument) { - log_debug("%s: -%c", __func__, - flag); - args_set(args, flag, NULL); - continue; - } - xasprintf(cause, - "-%c expects an argument", - flag); - args_free(args); - return (NULL); - } - if (values[i].type != ARGS_STRING) { - xasprintf(cause, - "-%c argument must be a string", - flag); - args_free(args); - return (NULL); - } - args_copy_value(new, &values[i++]); - } - s = args_value_as_string(new); - log_debug("%s: -%c = %s", __func__, flag, s); - args_set(args, flag, new); - break; + stop = args_parse_flags(parse, values, count, cause, args, &i); + if (stop == -1) { + args_free(args); + return (NULL); } + if (stop == 1) + break; } log_debug("%s: flags end at %u of %u", __func__, i, count); if (i != count) { @@ -323,13 +357,13 @@ args_copy(struct args *args, int argc, char **argv) RB_FOREACH(entry, args_tree, &args->tree) { if (TAILQ_EMPTY(&entry->values)) { for (i = 0; i < entry->count; i++) - args_set(new_args, entry->flag, NULL); + args_set(new_args, entry->flag, NULL, 0); continue; } TAILQ_FOREACH(value, &entry->values, entry) { new_value = xcalloc(1, sizeof *new_value); args_copy_copy_value(new_value, value, argc, argv); - args_set(new_args, entry->flag, new_value); + args_set(new_args, entry->flag, new_value, 0); } } if (args->count == 0) @@ -487,6 +521,7 @@ args_print(struct args *args) char *buf; u_int i, j; struct args_entry *entry; + struct args_entry *last = NULL; struct args_value *value; len = 1; @@ -494,6 +529,8 @@ args_print(struct args *args) /* Process the flags first. */ RB_FOREACH(entry, args_tree, &args->tree) { + if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) + continue; if (!TAILQ_EMPTY(&entry->values)) continue; @@ -505,6 +542,16 @@ args_print(struct args *args) /* Then the flags with arguments. */ RB_FOREACH(entry, args_tree, &args->tree) { + if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) { + if (*buf != '\0') + args_print_add(&buf, &len, " -%c", entry->flag); + else + args_print_add(&buf, &len, "-%c", entry->flag); + last = entry; + continue; + } + if (TAILQ_EMPTY(&entry->values)) + continue; TAILQ_FOREACH(value, &entry->values, entry) { if (*buf != '\0') args_print_add(&buf, &len, " -%c", entry->flag); @@ -512,7 +559,10 @@ args_print(struct args *args) args_print_add(&buf, &len, "-%c", entry->flag); args_print_add_value(&buf, &len, value); } + last = entry; } + if (last && (last->flags & ARGS_ENTRY_OPTIONAL_VALUE)) + args_print_add(&buf, &len, " --"); /* And finally the argument vector. */ for (i = 0; i < args->count; i++) @@ -582,7 +632,7 @@ args_has(struct args *args, u_char flag) /* Set argument value in the arguments tree. */ void -args_set(struct args *args, u_char flag, struct args_value *value) +args_set(struct args *args, u_char flag, struct args_value *value, int flags) { struct args_entry *entry; @@ -591,6 +641,7 @@ args_set(struct args *args, u_char flag, struct args_value *value) entry = xcalloc(1, sizeof *entry); entry->flag = flag; entry->count = 1; + entry->flags = flags; TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); } else diff --git a/cmd-find-window.c b/cmd-find-window.c index 6e07537c..cb9afacb 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -103,8 +103,8 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) new_args = args_create(); if (args_has(args, 'Z')) - args_set(new_args, 'Z', NULL); - args_set(new_args, 'f', filter); + args_set(new_args, 'Z', NULL, 0); + args_set(new_args, 'f', filter, 0); window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args); args_free(new_args); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index e22d94a6..2eed4ccd 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -33,13 +33,13 @@ const struct cmd_entry cmd_send_keys_entry = { .name = "send-keys", .alias = "send", - .args = { "FHlMN:Rt:X", 0, -1, NULL }, - .usage = "[-FHlMRX] [-N repeat-count] " CMD_TARGET_PANE_USAGE - " key ...", + .args = { "c:FHKlMN:Rt:X", 0, -1, NULL }, + .usage = "[-FHKlMRX] [-c target-client] [-N repeat-count] " + CMD_TARGET_PANE_USAGE " key ...", .target = { 't', CMD_FIND_PANE, 0 }, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG, .exec = cmd_send_keys_exec }; @@ -58,7 +58,7 @@ const struct cmd_entry cmd_send_prefix_entry = { static struct cmdq_item * cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after, - key_code key) + struct args *args, key_code key) { struct cmd_find_state *target = cmdq_get_target(item); struct client *tc = cmdq_get_target_client(item); @@ -66,8 +66,18 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after, struct winlink *wl = target->wl; struct window_pane *wp = target->wp; struct window_mode_entry *wme; - struct key_table *table; + struct key_table *table = NULL; struct key_binding *bd; + struct key_event *event; + + if (args_has(args, 'K')) { + event = xmalloc(sizeof *event); + event->key = key; + memset(&event->m, 0, sizeof event->m); + if (server_client_handle_key(tc, event) == 0) + free(event); + return (item); + } wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode->key_table == NULL) { @@ -102,14 +112,16 @@ cmd_send_keys_inject_string(struct cmdq_item *item, struct cmdq_item *after, n = strtol(s, &endptr, 16); if (*s =='\0' || n < 0 || n > 0xff || *endptr != '\0') return (item); - return (cmd_send_keys_inject_key(item, after, KEYC_LITERAL|n)); + return (cmd_send_keys_inject_key(item, after, args, + KEYC_LITERAL|n)); } literal = args_has(args, 'l'); if (!literal) { key = key_string_lookup_string(s); if (key != KEYC_NONE && key != KEYC_UNKNOWN) { - after = cmd_send_keys_inject_key(item, after, key); + after = cmd_send_keys_inject_key(item, after, args, + key); if (after != NULL) return (after); } @@ -125,7 +137,8 @@ cmd_send_keys_inject_string(struct cmdq_item *item, struct cmdq_item *after, continue; key = uc; } - after = cmd_send_keys_inject_key(item, after, key); + after = cmd_send_keys_inject_key(item, after, args, + key); } free(ud); } @@ -193,7 +206,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) key = options_get_number(s->options, "prefix2"); else key = options_get_number(s->options, "prefix"); - cmd_send_keys_inject_key(item, item, key); + cmd_send_keys_inject_key(item, item, args, key); return (CMD_RETURN_NORMAL); } @@ -207,7 +220,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'N') || args_has(args, 'R')) return (CMD_RETURN_NORMAL); for (; np != 0; np--) - cmd_send_keys_inject_key(item, NULL, event->key); + cmd_send_keys_inject_key(item, NULL, args, event->key); return (CMD_RETURN_NORMAL); } diff --git a/tmux.1 b/tmux.1 index 4f314110..2a2e54bf 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3212,13 +3212,14 @@ lists only the first matching key. lists the command for keys that do not have a note rather than skipping them. .Tg send .It Xo Ic send-keys -.Op Fl FHlMRX +.Op Fl FHKlMRX +.Op Fl c Ar target-client .Op Fl N Ar repeat-count .Op Fl t Ar target-pane .Ar key Ar ... .Xc .D1 Pq alias: Ic send -Send a key or keys to a window. +Send a key or keys to a window or client. Each argument .Ar key is the name of the key (such as @@ -3227,6 +3228,12 @@ or .Ql NPage ) to send; if the string is not recognised as a key, it is sent as a series of characters. +If +.Fl K +is given, keys are sent to +.Ar target-client , +so they are looked up in the client's key table, rather than to +.Ar target-pane . All arguments are sent sequentially from first to last. If no keys are given and the command is bound to a key, then that key is used. .Pp diff --git a/tmux.h b/tmux.h index 9a13162d..5b15c1e1 100644 --- a/tmux.h +++ b/tmux.h @@ -2387,7 +2387,7 @@ void tty_keys_free(struct tty *); int tty_keys_next(struct tty *); /* arguments.c */ -void args_set(struct args *, u_char, struct args_value *); +void args_set(struct args *, u_char, struct args_value *, int); struct args *args_create(void); struct args *args_parse(const struct args_parse *, struct args_value *, u_int, char **);