diff --git a/arguments.c b/arguments.c index 3315cef2..387790ac 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; }; @@ -94,6 +98,22 @@ args_copy_value(struct args_value *to, struct args_value *from) } } +/* Type to string. */ +static const char * +args_type_to_string (enum args_type type) +{ + switch (type) + { + case ARGS_NONE: + return "NONE"; + case ARGS_STRING: + return "STRING"; + case ARGS_COMMANDS: + return "COMMANDS"; + } + return "INVALID"; +} + /* Get value as string. */ static const char * args_value_as_string(struct args_value *value) @@ -122,6 +142,99 @@ 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 == 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[1] != ':') { + log_debug("%s: -%c", __func__, flag); + args_set(args, flag, NULL, 0); + continue; + } + optional_argument = (found[2] == ':'); + 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 +244,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) { @@ -218,8 +266,8 @@ args_parse(const struct args_parse *parse, struct args_value *values, value = &values[i]; s = args_value_as_string(value); - log_debug("%s: %u = %s (type %d)", __func__, i, s, - value->type); + log_debug("%s: %u = %s (type %s)", __func__, i, s, + args_type_to_string (value->type)); if (parse->cb != NULL) { type = parse->cb(args, args->count, cause); @@ -323,13 +371,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 +535,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 +543,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 +556,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 +573,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 +646,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 +655,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 @@ -747,6 +812,8 @@ args_make_commands(struct args_command_state *state, int argc, char **argv, } cmd = xstrdup(state->cmd); + log_debug("%s: %s", __func__, cmd); + cmd_log_argv(argc, argv, __func__); for (i = 0; i < argc; i++) { new_cmd = cmd_template_replace(cmd, argv[i], i + 1); log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd); diff --git a/client.c b/client.c index 7f712ffb..374c1146 100644 --- a/client.c +++ b/client.c @@ -700,6 +700,9 @@ client_dispatch_wait(struct imsg *imsg) !(client_flags & CLIENT_CONTROL), client_file_check_cb, NULL); break; + case MSG_READ_CANCEL: + file_read_cancel(&client_files, imsg); + break; case MSG_WRITE_OPEN: file_write_open(&client_files, client_peer, imsg, 1, !(client_flags & CLIENT_CONTROL), client_file_check_cb, diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 4f38d4bd..9c4b1508 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -115,6 +115,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) layout_init(w, wp); wp->flags |= PANE_CHANGED; + colour_palette_from_option(&wp->palette, wp->options); if (idx == -1) idx = -1 - options_get_number(dst_s->options, "base-index"); diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 4455856b..6010d0fd 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -179,10 +179,10 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, if (s == NULL) goto out; + if (done) { if (cdata->flags & PROMPT_INCREMENTAL) goto out; - cmd_append_argv(&cdata->argc, &cdata->argv, s); if (++cdata->current != cdata->count) { prompt = &cdata->prompts[cdata->current]; @@ -193,8 +193,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, argc = cdata->argc; argv = cmd_copy_argv(cdata->argc, cdata->argv); - cmd_append_argv(&argc, &argv, s); + if (!done) + cmd_append_argv(&argc, &argv, s); + if (done) { + cmd_free_argv(cdata->argc, cdata->argv); cdata->argc = argc; cdata->argv = cmd_copy_argv(argc, argv); } diff --git a/cmd-display-menu.c b/cmd-display-menu.c index e6a503b1..34f6d7bf 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -38,9 +38,10 @@ const struct cmd_entry cmd_display_menu_entry = { .name = "display-menu", .alias = "menu", - .args = { "c:t:OT:x:y:", 1, -1, cmd_display_menu_args_parse }, - .usage = "[-O] [-c target-client] " CMD_TARGET_PANE_USAGE " [-T title] " - "[-x position] [-y position] name key command ...", + .args = { "c:t:S:OT:x:y:", 1, -1, cmd_display_menu_args_parse }, + .usage = "[-O] [-c target-client] [-S starting-choice] " + CMD_TARGET_PANE_USAGE " [-T title] [-x position] " + "[-y position] name key command ...", .target = { 't', CMD_FIND_PANE, 0 }, @@ -274,6 +275,7 @@ cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h); free(p); + format_free(ft); return (1); } @@ -287,13 +289,27 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) struct menu *menu = NULL; struct menu_item menu_item; const char *key, *name; - char *title; - int flags = 0; + char *title, *cause; + int flags = 0, starting_choice = 0; u_int px, py, i, count = args_count(args); if (tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); + if (args_has(args, 'S')) { + if (strcmp(args_get(args, 'S'), "-") == 0) + starting_choice = -1; + else { + starting_choice = args_strtonum(args, 'S', 0, UINT_MAX, + &cause); + if (cause != NULL) { + cmdq_error(item, "starting choice %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + } + if (args_has(args, 'T')) title = format_single_from_target(item, args_get(args, 'T')); else @@ -340,8 +356,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) flags |= MENU_STAYOPEN; if (!event->m.valid) flags |= MENU_NOMOUSE; - if (menu_display(menu, flags, item, px, py, tc, target, NULL, - NULL) != 0) + if (menu_display(menu, flags, starting_choice, item, px, py, tc, target, + NULL, NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } @@ -454,11 +470,13 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) cmd_free_argv(argc, argv); if (env != NULL) environ_free(env); + free(cwd); free(title); return (CMD_RETURN_NORMAL); } if (env != NULL) environ_free(env); + free(cwd); free(title); cmd_free_argv(argc, argv); return (CMD_RETURN_WAIT); diff --git a/cmd-display-message.c b/cmd-display-message.c index f5e91020..512509f0 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -68,9 +68,10 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp; const char *template; char *msg, *cause; - int delay = -1, flags; + int delay = -1, flags, Nflag = args_has(args, 'N'); struct format_tree *ft; u_int count = args_count(args); + struct evbuffer *evb; if (args_has(args, 'I')) { if (wp == NULL) @@ -141,10 +142,15 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "%s", msg); else if (args_has(args, 'p')) cmdq_print(item, "%s", msg); - else if (tc != NULL) { - status_message_set(tc, delay, 0, args_has(args, 'N'), "%s", - msg); - } + else if (tc != NULL && (tc->flags & CLIENT_CONTROL)) { + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); + evbuffer_add_printf(evb, "%%message %s", msg); + server_client_print(tc, 0, evb); + evbuffer_free(evb); + } else if (tc != NULL) + status_message_set(tc, delay, 0, Nflag, "%s", msg); free(msg); format_free(ft); 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-join-pane.c b/cmd-join-pane.c index e82b4cde..da1ba9ae 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -155,6 +155,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) else TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); layout_assign_pane(lc, src_wp, 0); + colour_palette_from_option(&src_wp->palette, src_wp->options); recalculate_sizes(); diff --git a/cmd-list-clients.c b/cmd-list-clients.c index 53a99178..da7541bc 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -41,8 +41,8 @@ const struct cmd_entry cmd_list_clients_entry = { .name = "list-clients", .alias = "lsc", - .args = { "F:t:", 0, 0, NULL }, - .usage = "[-F format] " CMD_TARGET_SESSION_USAGE, + .args = { "F:f:t:", 0, 0, NULL }, + .usage = "[-F format] [-f filter] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -58,9 +58,10 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) struct client *c; struct session *s; struct format_tree *ft; - const char *template; + const char *template, *filter; u_int idx; - char *line; + char *line, *expanded; + int flag; if (args_has(args, 't')) s = target->s; @@ -69,6 +70,7 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) if ((template = args_get(args, 'F')) == NULL) template = LIST_CLIENTS_TEMPLATE; + filter = args_get(args, 'f'); idx = 0; TAILQ_FOREACH(c, &clients, entry) { @@ -79,9 +81,17 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) format_add(ft, "line", "%u", idx); format_defaults(ft, c, NULL, NULL, NULL); - line = format_expand(ft, template); - cmdq_print(item, "%s", line); - free(line); + if (filter != NULL) { + expanded = format_expand(ft, filter); + flag = format_true(expanded); + free(expanded); + } else + flag = 1; + if (flag) { + line = format_expand(ft, template); + cmdq_print(item, "%s", line); + free(line); + } format_free(ft); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index ae9f995c..395b147c 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -148,6 +148,7 @@ static enum cmd_retval cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); struct key_table *table; struct key_binding *bd; const char *tablename, *r, *keystr; @@ -296,9 +297,15 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) strlcat(tmp, cp, tmpsize); free(cp); - cmdq_print(item, "bind-key %s", tmp); - + if (args_has(args, '1') && tc != NULL) { + status_message_set(tc, -1, 1, 0, "bind-key %s", + tmp); + } else + cmdq_print(item, "bind-key %s", tmp); free(key); + + if (args_has(args, '1')) + break; bd = key_bindings_next(table, bd); } table = key_bindings_next_table(table); diff --git a/cmd-parse.y b/cmd-parse.y index cdf026f3..65ffad84 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -1615,13 +1615,24 @@ yylex_token(int ch) for (;;) { /* EOF or \n are always the end of the token. */ - if (ch == EOF || (state == NONE && ch == '\n')) + if (ch == EOF) { + log_debug("%s: end at EOF", __func__); break; + } + if (state == NONE && ch == '\n') { + log_debug("%s: end at EOL", __func__); + break; + } /* Whitespace or ; or } ends a token unless inside quotes. */ - if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') && - state == NONE) + if (state == NONE && (ch == ' ' || ch == '\t')) { + log_debug("%s: end at WS", __func__); break; + } + if (state == NONE && (ch == ';' || ch == '}')) { + log_debug("%s: end at %c", __func__, ch); + break; + } /* * Spaces and comments inside quotes after \n are removed but diff --git a/cmd-queue.c b/cmd-queue.c index 8325e2e8..e5188c54 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -821,45 +821,30 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags) control_write(c, "%%%s %ld %u %d", guard, t, number, flags); } +/* Show message from command. */ +void +cmdq_print_data(struct cmdq_item *item, int parse, struct evbuffer *evb) +{ + server_client_print(item->client, parse, evb); +} + /* Show message from command. */ void cmdq_print(struct cmdq_item *item, const char *fmt, ...) { - struct client *c = item->client; - struct window_pane *wp; - struct window_mode_entry *wme; - va_list ap; - char *tmp, *msg; + va_list ap; + struct evbuffer *evb; + + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); va_start(ap, fmt); - xvasprintf(&msg, fmt, ap); + evbuffer_add_vprintf(evb, fmt, ap); va_end(ap); - log_debug("%s: %s", __func__, msg); - - if (c == NULL) - /* nothing */; - else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { - if (~c->flags & CLIENT_UTF8) { - tmp = msg; - msg = utf8_sanitize(tmp); - free(tmp); - } - if (c->flags & CLIENT_CONTROL) - control_write(c, "%s", msg); - else - file_print(c, "%s\n", msg); - } else { - wp = server_client_get_pane(c); - wme = TAILQ_FIRST(&wp->modes); - if (wme == NULL || wme->mode != &window_view_mode) { - window_pane_set_mode(wp, NULL, &window_view_mode, NULL, - NULL); - } - window_copy_add(wp, 0, "%s", msg); - } - - free(msg); + cmdq_print_data(item, 0, evb); + evbuffer_free(evb); } /* Show error from command. */ diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 7d678372..3e81500d 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -78,7 +78,8 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) int flags; const char *bufname = args_get(args, 'b'), *bufdata; size_t bufsize; - char *path, *tmp; + char *path; + struct evbuffer *evb; if (bufname == NULL) { if ((pb = paste_get_top(NULL)) == NULL) { @@ -96,10 +97,12 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_show_buffer_entry) { if (c->session != NULL || (c->flags & CLIENT_CONTROL)) { - utf8_stravisx(&tmp, bufdata, bufsize, - VIS_OCTAL|VIS_CSTYLE|VIS_TAB); - cmdq_print(item, "%s", tmp); - free(tmp); + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); + evbuffer_add(evb, bufdata, bufsize); + cmdq_print_data(item, 1, evb); + evbuffer_free(evb); return (CMD_RETURN_NORMAL); } path = xstrdup("-"); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index e22d94a6..ac99a6fd 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|CMD_CLIENT_CANFAIL, .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,20 @@ 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')) { + if (tc == NULL) + return (item); + event = xmalloc(sizeof *event); + event->key = key|KEYC_SENT; + 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 +114,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 +139,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 +208,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 +222,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/cmd-swap-pane.c b/cmd-swap-pane.c index 4191b894..80c20c80 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -132,6 +132,8 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) src_w->last = NULL; if (dst_w->last == dst_wp) dst_w->last = NULL; + colour_palette_from_option(&src_wp->palette, src_wp->options); + colour_palette_from_option(&dst_wp->palette, dst_wp->options); } server_redraw_window(src_w); server_redraw_window(dst_w); diff --git a/colour.c b/colour.c index a282d182..9bde646f 100644 --- a/colour.c +++ b/colour.c @@ -960,6 +960,47 @@ colour_byname(const char *name) return (-1); } +/* Parse colour from an X11 string. */ +int +colour_parseX11(const char *p) +{ + double c, m, y, k = 0; + u_int r, g, b; + size_t len = strlen(p); + int colour = -1; + char *copy; + + if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || + (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || + sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) + colour = colour_join_rgb(r, g, b); + else if ((len == 18 && + sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || + (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) + colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); + else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || + sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && + c >= 0 && c <= 1 && m >= 0 && m <= 1 && + y >= 0 && y <= 1 && k >= 0 && k <= 1) { + colour = colour_join_rgb( + (1 - c) * (1 - k) * 255, + (1 - m) * (1 - k) * 255, + (1 - y) * (1 - k) * 255); + } else { + while (len != 0 && *p == ' ') { + p++; + len--; + } + while (len != 0 && p[len - 1] == ' ') + len--; + copy = xstrndup(p, len); + colour = colour_byname(copy); + free(copy); + } + log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); + return (colour); +} + /* Initialize palette. */ void colour_palette_init(struct colour_palette *p) @@ -1069,5 +1110,4 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo) } a = options_array_next(a); } - } diff --git a/file.c b/file.c index 04a907bf..3c1096be 100644 --- a/file.c +++ b/file.c @@ -149,7 +149,8 @@ file_fire_done_cb(__unused int fd, __unused short events, void *arg) struct client_file *cf = arg; struct client *c = cf->c; - if (cf->cb != NULL && (c == NULL || (~c->flags & CLIENT_DEAD))) + if (cf->cb != NULL && + (cf->closed || c == NULL || (~c->flags & CLIENT_DEAD))) cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data); file_free(cf); } @@ -352,7 +353,7 @@ done: } /* Read a file. */ -void +struct client_file * file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) { struct client_file *cf; @@ -420,10 +421,27 @@ skip: goto done; } free(msg); - return; + return cf; done: file_fire_done(cf); + return NULL; +} + +/* Cancel a file read. */ +void +file_cancel(struct client_file *cf) +{ + struct msg_read_cancel msg; + + log_debug("read cancel file %d", cf->stream); + + if (cf->closed) + return; + cf->closed = 1; + + msg.stream = cf->stream; + proc_send(cf->peer, MSG_READ_CANCEL, -1, &msg, sizeof msg); } /* Push event, fired if there is more writing to be done. */ @@ -757,6 +775,24 @@ reply: proc_send(peer, MSG_READ_DONE, -1, &reply, sizeof reply); } +/* Handle a read cancel message (client). */ +void +file_read_cancel(struct client_files *files, struct imsg *imsg) +{ + struct msg_read_cancel *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_READ_CANCEL size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("cancel file %d", cf->stream); + + file_read_error_callback(NULL, 0, cf); +} + /* Handle a write ready message (server). */ void file_write_ready(struct client_files *files, struct imsg *imsg) @@ -794,7 +830,7 @@ file_read_data(struct client_files *files, struct imsg *imsg) return; log_debug("file %d read %zu bytes", cf->stream, bsize); - if (cf->error == 0) { + if (cf->error == 0 && !cf->closed) { if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { cf->error = ENOMEM; file_fire_done(cf); diff --git a/format.c b/format.c index ee6930b0..547f4e1a 100644 --- a/format.c +++ b/format.c @@ -103,6 +103,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_SESSION_NAME 0x8000 #define FORMAT_CHARACTER 0x10000 #define FORMAT_COLOUR 0x20000 +#define FORMAT_CLIENTS 0x40000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -3747,7 +3748,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, cp++; /* Check single character modifiers with no arguments. */ - if (strchr("labcdnwETSWP<>", cp[0]) != NULL && + if (strchr("labcdnwETSWPL<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -4075,6 +4076,40 @@ format_loop_panes(struct format_expand_state *es, const char *fmt) return (value); } +/* Loop over clients. */ +static char * +format_loop_clients(struct format_expand_state *es, const char *fmt) +{ + struct format_tree *ft = es->ft; + struct client *c = ft->client; + struct cmdq_item *item = ft->item; + struct format_tree *nft; + struct format_expand_state next; + char *expanded, *value; + size_t valuelen; + + value = xcalloc(1, 1); + valuelen = 1; + + TAILQ_FOREACH(c, &clients, entry) { + format_log(es, "client loop: %s", c->name); + nft = format_create(c, item, 0, ft->flags); + format_defaults(nft, c, ft->s, ft->wl, ft->wp); + format_copy_state(&next, es, 0); + next.ft = nft; + expanded = format_expand1(&next, fmt); + format_free(nft); + + valuelen += strlen(expanded); + value = xrealloc(value, valuelen); + + strlcat(value, expanded, valuelen); + free(expanded); + } + + return (value); +} + static char * format_replace_expression(struct format_modifier *mexp, struct format_expand_state *es, const char *copy) @@ -4349,6 +4384,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, case 'P': modifiers |= FORMAT_PANES; break; + case 'L': + modifiers |= FORMAT_CLIENTS; + break; } } else if (fm->size == 2) { if (strcmp(fm->modifier, "||") == 0 || @@ -4405,6 +4443,10 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, value = format_loop_panes(es, copy); if (value == NULL) goto fail; + } else if (modifiers & FORMAT_CLIENTS) { + value = format_loop_clients(es, copy); + if (value == NULL) + goto fail; } else if (modifiers & FORMAT_WINDOW_NAME) { value = format_window_name(es, copy); if (value == NULL) diff --git a/input-keys.c b/input-keys.c index a41414db..0451b968 100644 --- a/input-keys.c +++ b/input-keys.c @@ -496,6 +496,9 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) ike = input_key_get(key & ~KEYC_EXTENDED); if (ike != NULL) { log_debug("found key 0x%llx: \"%s\"", key, ike->data); + if ((key == KEYC_PASTE_START || key == KEYC_PASTE_END) && + (~s->mode & MODE_BRACKETPASTE)) + return (0); if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META)) input_key_write(__func__, bev, "\033", 1); input_key_write(__func__, bev, ike->data, strlen(ike->data)); diff --git a/input.c b/input.c index 419c2456..42862d8f 100644 --- a/input.c +++ b/input.c @@ -1086,6 +1086,7 @@ input_reply(struct input_ctx *ictx, const char *fmt, ...) xvasprintf(&reply, fmt, ap); va_end(ap); + log_debug("%s: %s", __func__, reply); bufferevent_write(bev, reply, strlen(reply)); free(reply); } @@ -2466,47 +2467,6 @@ input_top_bit_set(struct input_ctx *ictx) return (0); } -/* Parse colour from OSC. */ -static int -input_osc_parse_colour(const char *p) -{ - double c, m, y, k = 0; - u_int r, g, b; - size_t len = strlen(p); - int colour = -1; - char *copy; - - if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || - (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || - sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) - colour = colour_join_rgb(r, g, b); - else if ((len == 18 && - sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || - (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) - colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); - else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || - sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && - c >= 0 && c <= 1 && m >= 0 && m <= 1 && - y >= 0 && y <= 1 && k >= 0 && k <= 1) { - colour = colour_join_rgb( - (1 - c) * (1 - k) * 255, - (1 - m) * (1 - k) * 255, - (1 - y) * (1 - k) * 255); - } else { - while (len != 0 && *p == ' ') { - p++; - len--; - } - while (len != 0 && p[len - 1] == ' ') - len--; - copy = xstrndup(p, len); - colour = colour_byname(copy); - free(copy); - } - log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); - return (colour); -} - /* Reply to a colour request. */ static void input_osc_colour_reply(struct input_ctx *ictx, u_int n, int c) @@ -2555,7 +2515,7 @@ input_osc_4(struct input_ctx *ictx, const char *p) input_osc_colour_reply(ictx, 4, c); continue; } - if ((c = input_osc_parse_colour(s)) == -1) { + if ((c = colour_parseX11(s)) == -1) { s = next; continue; } @@ -2611,6 +2571,47 @@ bad: free(id); } +/* + * Get a client with a foreground for the pane. There isn't much to choose + * between them so just use the first. + */ +static int +input_get_fg_client(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + if (loop->tty.fg == -1) + continue; + return (loop->tty.fg); + } + return (-1); +} + +/* Get a client with a background for the pane. */ +static int +input_get_bg_client(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + if (loop->tty.bg == -1) + continue; + return (loop->tty.bg); + } + return (-1); +} + /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void input_osc_10(struct input_ctx *ictx, const char *p) @@ -2620,14 +2621,18 @@ input_osc_10(struct input_ctx *ictx, const char *p) int c; if (strcmp(p, "?") == 0) { - if (wp != NULL) { - tty_default_colours(&defaults, wp); - input_osc_colour_reply(ictx, 10, defaults.fg); - } + if (wp == NULL) + return; + tty_default_colours(&defaults, wp); + if (COLOUR_DEFAULT(defaults.fg)) + c = input_get_fg_client(wp); + else + c = defaults.fg; + input_osc_colour_reply(ictx, 10, c); return; } - if ((c = input_osc_parse_colour(p)) == -1) { + if ((c = colour_parseX11(p)) == -1) { log_debug("bad OSC 10: %s", p); return; } @@ -2664,14 +2669,18 @@ input_osc_11(struct input_ctx *ictx, const char *p) int c; if (strcmp(p, "?") == 0) { - if (wp != NULL) { - tty_default_colours(&defaults, wp); - input_osc_colour_reply(ictx, 11, defaults.bg); - } + if (wp == NULL) + return; + tty_default_colours(&defaults, wp); + if (COLOUR_DEFAULT(defaults.bg)) + c = input_get_bg_client(wp); + else + c = defaults.bg; + input_osc_colour_reply(ictx, 11, c); return; } - if ((c = input_osc_parse_colour(p)) == -1) { + if ((c = colour_parseX11(p)) == -1) { log_debug("bad OSC 11: %s", p); return; } @@ -2716,7 +2725,7 @@ input_osc_12(struct input_ctx *ictx, const char *p) return; } - if ((c = input_osc_parse_colour(p)) == -1) { + if ((c = colour_parseX11(p)) == -1) { log_debug("bad OSC 12: %s", p); return; } diff --git a/key-bindings.c b/key-bindings.c index 528e0b73..d0697544 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -377,7 +377,7 @@ key_bindings_init(void) "bind -N 'Move to the previously active pane' \\; { last-pane }", "bind -N 'Choose a paste buffer from a list' = { choose-buffer -Z }", "bind -N 'List key bindings' ? { list-keys -N }", - "bind -N 'Choose a client from a list' D { choose-client -Z }", + "bind -N 'Choose and detach a client from a list' D { choose-client -Z }", "bind -N 'Spread panes out evenly' E { select-layout -E }", "bind -N 'Switch to the last client' L { switch-client -l }", "bind -N 'Clear the marked pane' M { select-pane -M }", diff --git a/key-string.c b/key-string.c index 086c3ac4..699d460f 100644 --- a/key-string.c +++ b/key-string.c @@ -462,6 +462,8 @@ out: strlcat(out, "B", sizeof out); if (saved & KEYC_EXTENDED) strlcat(out, "E", sizeof out); + if (saved & KEYC_SENT) + strlcat(out, "S", sizeof out); strlcat(out, "]", sizeof out); } return (out); diff --git a/layout-custom.c b/layout-custom.c index 932b30e7..d7be5b18 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -162,8 +162,10 @@ layout_parse(struct window *w, const char *layout, char **cause) u_short csum; /* Check validity. */ - if (sscanf(layout, "%hx,", &csum) != 1) + if (sscanf(layout, "%hx,", &csum) != 1) { + *cause = xstrdup("invalid layout"); return (-1); + } layout += 5; if (csum != layout_checksum(layout)) { *cause = xstrdup("invalid layout"); diff --git a/menu.c b/menu.c index 4aad1d8c..288030b2 100644 --- a/menu.c +++ b/menu.c @@ -64,6 +64,8 @@ menu_add_item(struct menu *menu, const struct menu_item *item, line = (item == NULL || item->name == NULL || *item->name == '\0'); if (line && menu->count == 0) return; + if (line && menu->items[menu->count - 1].name == NULL) + return; menu->items = xreallocarray(menu->items, menu->count + 1, sizeof *menu->items); @@ -427,12 +429,12 @@ chosen: } struct menu_data * -menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, - u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, - void *data) +menu_prepare(struct menu *menu, int flags, int starting_choice, + struct cmdq_item *item, u_int px, u_int py, struct client *c, + struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; - u_int i; + int choice; const char *name; if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) @@ -457,18 +459,38 @@ menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, md->py = py; md->menu = menu; + md->choice = -1; + if (md->flags & MENU_NOMOUSE) { - for (i = 0; i < menu->count; i++) { - name = menu->items[i].name; - if (name != NULL && *name != '-') - break; + if (starting_choice >= (int)menu->count) { + starting_choice = menu->count - 1; + choice = starting_choice + 1; + for (;;) { + name = menu->items[choice - 1].name; + if (name != NULL && *name != '-') { + md->choice = choice - 1; + break; + } + if (--choice == 0) + choice = menu->count; + if (choice == starting_choice + 1) + break; + } + } else if (starting_choice >= 0) { + choice = starting_choice; + for (;;) { + name = menu->items[choice].name; + if (name != NULL && *name != '-') { + md->choice = choice; + break; + } + if (++choice == (int)menu->count) + choice = 0; + if (choice == starting_choice) + break; + } } - if (i != menu->count) - md->choice = i; - else - md->choice = -1; - } else - md->choice = -1; + } md->cb = cb; md->data = data; @@ -476,13 +498,14 @@ menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, } int -menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, - u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, - void *data) +menu_display(struct menu *menu, int flags, int starting_choice, + struct cmdq_item *item, u_int px, u_int py, struct client *c, + struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; - md = menu_prepare(menu, flags, item, px, py, c, fs, cb, data); + md = menu_prepare(menu, flags, starting_choice, item, px, py, c, fs, cb, + data); if (md == NULL) return (-1); server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, diff --git a/mode-tree.c b/mode-tree.c index c007e27f..9d465e7b 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -962,8 +962,8 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, x -= (menu->width + 4) / 2; else x = 0; - if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback, - mtm) != 0) + if (menu_display(menu, 0, 0, NULL, x, y, c, NULL, + mode_tree_menu_callback, mtm) != 0) menu_free(menu); } diff --git a/paste.c b/paste.c index 84cdecb0..608ac9c6 100644 --- a/paste.c +++ b/paste.c @@ -240,11 +240,8 @@ paste_rename(const char *oldname, const char *newname, char **cause) } pb_new = paste_get_name(newname); - if (pb_new != NULL) { - if (cause != NULL) - xasprintf(cause, "buffer %s already exists", newname); - return (-1); - } + if (pb_new != NULL) + paste_free(pb_new); RB_REMOVE(paste_name_tree, &paste_by_name, pb); diff --git a/popup.c b/popup.c index 2e57153d..783f8f54 100644 --- a/popup.c +++ b/popup.c @@ -252,6 +252,7 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, palette); } + screen_free(&s); if (pd->md != NULL) { c->overlay_check = NULL; c->overlay_data = NULL; @@ -573,7 +574,7 @@ menu: x = m->x - (pd->menu->width + 4) / 2; else x = 0; - pd->md = menu_prepare(pd->menu, 0, NULL, x, m->y, c, NULL, + pd->md = menu_prepare(pd->menu, 0, 0, NULL, x, m->y, c, NULL, popup_menu_done, pd); c->flags |= CLIENT_REDRAWOVERLAY; diff --git a/regress/input-keys.sh b/regress/input-keys.sh new file mode 100644 index 00000000..262d12a6 --- /dev/null +++ b/regress/input-keys.sh @@ -0,0 +1,299 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null + +$TMUX -f/dev/null new -x20 -y2 -d || exit 1 + +sleep 0.1 + +exit_status=0 + +assert_key () { + key=$1 + expected_code=$2 + + $TMUX new-window -- sh -c 'stty raw -echo && cat -tv' + $TMUX send-keys "$key" $ + + actual_code=$($TMUX capturep -p | head -1 | sed -e 's/\$$//') + $TMUX kill-window + + if [ "$actual_code" = "$expected_code" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] $key -> $actual_code" + fi + else + echo "[FAIL] $key -> $expected_code (Got: $actual_code)" + exit_status=1 + fi + + shift + shift + + if [ "$1" = "--" ]; then + shift + assert_key "$@" + fi +} + +assert_key 'C-Space' '^@' +assert_key 'C-a' '^A' -- 'M-C-a' '^[^A' +assert_key 'C-b' '^B' -- 'M-C-b' '^[^B' +assert_key 'C-c' '^C' -- 'M-C-c' '^[^C' +assert_key 'C-d' '^D' -- 'M-C-d' '^[^D' +assert_key 'C-e' '^E' -- 'M-C-e' '^[^E' +assert_key 'C-f' '^F' -- 'M-C-f' '^[^F' +assert_key 'C-g' '^G' -- 'M-C-g' '^[^G' +assert_key 'C-h' '^H' -- 'M-C-h' '^[^H' +assert_key 'C-i' '^I' -- 'M-C-i' '^[^I' +assert_key 'C-j' '' -- 'M-C-j' '^[' # NL +assert_key 'C-k' '^K' -- 'M-C-k' '^[^K' +assert_key 'C-l' '^L' -- 'M-C-l' '^[^L' +assert_key 'C-m' '^M' -- 'M-C-m' '^[^M' +assert_key 'C-n' '^N' -- 'M-C-n' '^[^N' +assert_key 'C-o' '^O' -- 'M-C-o' '^[^O' +assert_key 'C-p' '^P' -- 'M-C-p' '^[^P' +assert_key 'C-q' '^Q' -- 'M-C-q' '^[^Q' +assert_key 'C-r' '^R' -- 'M-C-r' '^[^R' +assert_key 'C-s' '^S' -- 'M-C-s' '^[^S' +assert_key 'C-t' '^T' -- 'M-C-t' '^[^T' +assert_key 'C-u' '^U' -- 'M-C-u' '^[^U' +assert_key 'C-v' '^V' -- 'M-C-v' '^[^V' +assert_key 'C-w' '^W' -- 'M-C-w' '^[^W' +assert_key 'C-x' '^X' -- 'M-C-x' '^[^X' +assert_key 'C-y' '^Y' -- 'M-C-y' '^[^Y' +assert_key 'C-z' '^Z' -- 'M-C-z' '^[^Z' +assert_key 'Escape' '^[' -- 'M-Escape' '^[^[' +assert_key "C-\\" "^\\" -- "M-C-\\" "^[^\\" +assert_key 'C-]' '^]' -- 'M-C-]' '^[^]' +assert_key 'C-^' '^^' -- 'M-C-^' '^[^^' +assert_key 'C-_' '^_' -- 'M-C-_' '^[^_' +assert_key 'Space' ' ' -- 'M-Space' '^[ ' +assert_key '!' '!' -- 'M-!' '^[!' +assert_key '"' '"' -- 'M-"' '^["' +assert_key '#' '#' -- 'M-#' '^[#' +assert_key '$' '$' -- 'M-$' '^[$' +assert_key '%' '%' -- 'M-%' '^[%' +assert_key '&' '&' -- 'M-&' '^[&' +assert_key "'" "'" -- "M-'" "^['" +assert_key '(' '(' -- 'M-(' '^[(' +assert_key ')' ')' -- 'M-)' '^[)' +assert_key '*' '*' -- 'M-*' '^[*' +assert_key '+' '+' -- 'M-+' '^[+' +assert_key ',' ',' -- 'M-,' '^[,' +assert_key '-' '-' -- 'M--' '^[-' +assert_key '.' '.' -- 'M-.' '^[.' +assert_key '/' '/' -- 'M-/' '^[/' +assert_key '0' '0' -- 'M-0' '^[0' +assert_key '1' '1' -- 'M-1' '^[1' +assert_key '2' '2' -- 'M-2' '^[2' +assert_key '3' '3' -- 'M-3' '^[3' +assert_key '4' '4' -- 'M-4' '^[4' +assert_key '5' '5' -- 'M-5' '^[5' +assert_key '6' '6' -- 'M-6' '^[6' +assert_key '7' '7' -- 'M-7' '^[7' +assert_key '8' '8' -- 'M-8' '^[8' +assert_key '9' '9' -- 'M-9' '^[9' +assert_key ':' ':' -- 'M-:' '^[:' +assert_key '\;' ';' -- 'M-\;' '^[;' +assert_key '<' '<' -- 'M-<' '^[<' +assert_key '=' '=' -- 'M-=' '^[=' +assert_key '>' '>' -- 'M->' '^[>' +assert_key '?' '?' -- 'M-?' '^[?' +assert_key '@' '@' -- 'M-@' '^[@' +assert_key 'A' 'A' -- 'M-A' '^[A' +assert_key 'B' 'B' -- 'M-B' '^[B' +assert_key 'C' 'C' -- 'M-C' '^[C' +assert_key 'D' 'D' -- 'M-D' '^[D' +assert_key 'E' 'E' -- 'M-E' '^[E' +assert_key 'F' 'F' -- 'M-F' '^[F' +assert_key 'G' 'G' -- 'M-G' '^[G' +assert_key 'H' 'H' -- 'M-H' '^[H' +assert_key 'I' 'I' -- 'M-I' '^[I' +assert_key 'J' 'J' -- 'M-J' '^[J' +assert_key 'K' 'K' -- 'M-K' '^[K' +assert_key 'L' 'L' -- 'M-L' '^[L' +assert_key 'M' 'M' -- 'M-M' '^[M' +assert_key 'N' 'N' -- 'M-N' '^[N' +assert_key 'O' 'O' -- 'M-O' '^[O' +assert_key 'P' 'P' -- 'M-P' '^[P' +assert_key 'Q' 'Q' -- 'M-Q' '^[Q' +assert_key 'R' 'R' -- 'M-R' '^[R' +assert_key 'S' 'S' -- 'M-S' '^[S' +assert_key 'T' 'T' -- 'M-T' '^[T' +assert_key 'U' 'U' -- 'M-U' '^[U' +assert_key 'V' 'V' -- 'M-V' '^[V' +assert_key 'W' 'W' -- 'M-W' '^[W' +assert_key 'X' 'X' -- 'M-X' '^[X' +assert_key 'Y' 'Y' -- 'M-Y' '^[Y' +assert_key 'Z' 'Z' -- 'M-Z' '^[Z' +assert_key '[' '[' -- 'M-[' '^[[' +assert_key "\\" "\\" -- "M-\\" "^[\\" +assert_key ']' ']' -- 'M-]' '^[]' +assert_key '^' '^' -- 'M-^' '^[^' +assert_key '_' '_' -- 'M-_' '^[_' +assert_key '`' '`' -- 'M-`' '^[`' +assert_key 'a' 'a' -- 'M-a' '^[a' +assert_key 'b' 'b' -- 'M-b' '^[b' +assert_key 'c' 'c' -- 'M-c' '^[c' +assert_key 'd' 'd' -- 'M-d' '^[d' +assert_key 'e' 'e' -- 'M-e' '^[e' +assert_key 'f' 'f' -- 'M-f' '^[f' +assert_key 'g' 'g' -- 'M-g' '^[g' +assert_key 'h' 'h' -- 'M-h' '^[h' +assert_key 'i' 'i' -- 'M-i' '^[i' +assert_key 'j' 'j' -- 'M-j' '^[j' +assert_key 'k' 'k' -- 'M-k' '^[k' +assert_key 'l' 'l' -- 'M-l' '^[l' +assert_key 'm' 'm' -- 'M-m' '^[m' +assert_key 'n' 'n' -- 'M-n' '^[n' +assert_key 'o' 'o' -- 'M-o' '^[o' +assert_key 'p' 'p' -- 'M-p' '^[p' +assert_key 'q' 'q' -- 'M-q' '^[q' +assert_key 'r' 'r' -- 'M-r' '^[r' +assert_key 's' 's' -- 'M-s' '^[s' +assert_key 't' 't' -- 'M-t' '^[t' +assert_key 'u' 'u' -- 'M-u' '^[u' +assert_key 'v' 'v' -- 'M-v' '^[v' +assert_key 'w' 'w' -- 'M-w' '^[w' +assert_key 'x' 'x' -- 'M-x' '^[x' +assert_key 'y' 'y' -- 'M-y' '^[y' +assert_key 'z' 'z' -- 'M-z' '^[z' +assert_key '{' '{' -- 'M-{' '^[{' +assert_key '|' '|' -- 'M-|' '^[|' +assert_key '}' '}' -- 'M-}' '^[}' +assert_key '~' '~' -- 'M-~' '^[~' + +assert_key 'Tab' '^I' -- 'M-Tab' '^[^I' +assert_key 'BSpace' '^?' -- 'M-BSpace' '^[^?' + +## These cannot be sent, is that intentional? +## assert_key 'PasteStart' "^[[200~" +## assert_key 'PasteEnd' "^[[201~" + +assert_key 'F1' "^[OP" +assert_key 'F2' "^[OQ" +assert_key 'F3' "^[OR" +assert_key 'F4' "^[OS" +assert_key 'F5' "^[[15~" +assert_key 'F6' "^[[17~" +assert_key 'F8' "^[[19~" +assert_key 'F9' "^[[20~" +assert_key 'F10' "^[[21~" +assert_key 'F11' "^[[23~" +assert_key 'F12' "^[[24~" + +assert_key 'IC' '^[[2~' +assert_key 'Insert' '^[[2~' +assert_key 'DC' '^[[3~' +assert_key 'Delete' '^[[3~' + +## Why do these differ from tty-keys? +assert_key 'Home' '^[[1~' +assert_key 'End' '^[[4~' + +assert_key 'NPage' '^[[6~' +assert_key 'PageDown' '^[[6~' +assert_key 'PgDn' '^[[6~' +assert_key 'PPage' '^[[5~' +assert_key 'PageUp' '^[[5~' +assert_key 'PgUp' '^[[5~' + +assert_key 'BTab' '^[[Z' +assert_key 'C-S-Tab' '^[[Z' + +assert_key 'Up' '^[[A' +assert_key 'Down' '^[[B' +assert_key 'Right' '^[[C' +assert_key 'Left' '^[[D' + +# assert_key 'KPEnter' +assert_key 'KP*' '*' -- 'M-KP*' '^[*' +assert_key 'KP+' '+' -- 'M-KP+' '^[+' +assert_key 'KP-' '-' -- 'M-KP-' '^[-' +assert_key 'KP.' '.' -- 'M-KP.' '^[.' +assert_key 'KP/' '/' -- 'M-KP/' '^[/' +assert_key 'KP0' '0' -- 'M-KP0' '^[0' +assert_key 'KP1' '1' -- 'M-KP1' '^[1' +assert_key 'KP2' '2' -- 'M-KP2' '^[2' +assert_key 'KP3' '3' -- 'M-KP3' '^[3' +assert_key 'KP4' '4' -- 'M-KP4' '^[4' +assert_key 'KP5' '5' -- 'M-KP5' '^[5' +assert_key 'KP6' '6' -- 'M-KP6' '^[6' +assert_key 'KP7' '7' -- 'M-KP7' '^[7' +assert_key 'KP8' '8' -- 'M-KP8' '^[8' +assert_key 'KP9' '9' -- 'M-KP9' '^[9' + +# Extended keys +$TMUX set -g extended-keys always + +assert_extended_key () { + extended_key=$1 + expected_code_pattern=$2 + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;2/') + assert_key "S-$extended_key" "$expected_code" + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;3/') + assert_key "M-$extended_key" "$expected_code" + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;4/') + assert_key "S-M-$extended_key" "$expected_code" + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;5/') + assert_key "C-$extended_key" "$expected_code" + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;6/') + assert_key "S-C-$extended_key" "$expected_code" + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;7/') + assert_key "C-M-$extended_key" "$expected_code" + + expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;8/') + assert_key "S-C-M-$extended_key" "$expected_code" +} + +## Many of these pass without extended keys enabled -- are they extended keys? +assert_extended_key 'F1' '^[[1;_P' +assert_extended_key 'F2' "^[[1;_Q" +assert_extended_key 'F3' "^[[1;_R" +assert_extended_key 'F4' "^[[1;_S" +assert_extended_key 'F5' "^[[15;_~" +assert_extended_key 'F6' "^[[17;_~" +assert_extended_key 'F8' "^[[19;_~" +assert_extended_key 'F9' "^[[20;_~" +assert_extended_key 'F10' "^[[21;_~" +assert_extended_key 'F11' "^[[23;_~" +assert_extended_key 'F12' "^[[24;_~" + +assert_extended_key 'Up' '^[[1;_A' +assert_extended_key 'Down' '^[[1;_B' +assert_extended_key 'Right' '^[[1;_C' +assert_extended_key 'Left' '^[[1;_D' + +assert_extended_key 'Home' '^[[1;_H' +assert_extended_key 'End' '^[[1;_F' + +assert_extended_key 'PPage' '^[[5;_~' +assert_extended_key 'PageUp' '^[[5;_~' +assert_extended_key 'PgUp' '^[[5;_~' +assert_extended_key 'NPage' '^[[6;_~' +assert_extended_key 'PageDown' '^[[6;_~' +assert_extended_key 'PgDn' '^[[6;_~' + +assert_extended_key 'IC' '^[[2;_~' +assert_extended_key 'Insert' '^[[2;_~' +assert_extended_key 'DC' '^[[3;_~' +assert_extended_key 'Delete' '^[[3;_~' + +assert_key 'C-Tab' "^[[9;5u" +assert_key 'C-S-Tab' "^[[1;5Z" + +$TMUX kill-server 2>/dev/null + +exit $exit_status diff --git a/regress/tty-keys.sh b/regress/tty-keys.sh new file mode 100644 index 00000000..1fcc3657 --- /dev/null +++ b/regress/tty-keys.sh @@ -0,0 +1,361 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null +TMUX2="$TEST_TMUX -Ltest2" +$TMUX2 kill-server 2>/dev/null + +TMP=$(mktemp) +trap "rm -f $TMP" 0 1 15 + +$TMUX2 -f/dev/null new -d || exit 1 +$TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1 +sleep 0.1 + +exit_status=0 + +format_string () { + case $1 in + *\') + printf '"%%%%"' + ;; + *) + printf "'%%%%'" + ;; + esac +} + +assert_key () { + keys=$1 + expected_name=$2 + format_string=$(format_string "$expected_name") + + $TMUX2 command-prompt -k 'display-message -pl '"$format_string" > "$TMP" & + sleep 0.05 + + $TMUX send-keys $keys + + wait + + keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]') + actual_name=$(tr -d '[:space:]' < "$TMP") + + if [ "$actual_name" = "$expected_name" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] $keys -> $actual_name" + fi + else + echo "[FAIL] $keys -> $expected_name (Got: '$actual_name')" + exit_status=1 + fi + + if [ "$3" = "--" ]; then + shift; shift; shift + assert_key "$@" + fi + +} + +assert_key 0x00 'C-Space' # -- 'Escape 0x00' 'M-C-Space' +assert_key 0x01 'C-a' -- 'Escape 0x01' 'M-C-a' +assert_key 0x02 'C-b' -- 'Escape 0x02' 'M-C-b' +assert_key 0x03 'C-c' -- 'Escape 0x03' 'M-C-c' +assert_key 0x04 'C-d' -- 'Escape 0x04' 'M-C-d' +assert_key 0x05 'C-e' -- 'Escape 0x05' 'M-C-e' +assert_key 0x06 'C-f' -- 'Escape 0x06' 'M-C-f' +assert_key 0x07 'C-g' -- 'Escape 0x07' 'M-C-g' +assert_key 0x08 'C-h' -- 'Escape 0x08' 'M-C-h' +assert_key 0x09 'Tab' -- 'Escape 0x09' 'M-Tab' +assert_key 0x0A 'C-j' -- 'Escape 0x0A' 'M-C-j' +assert_key 0x0B 'C-k' -- 'Escape 0x0B' 'M-C-k' +assert_key 0x0C 'C-l' -- 'Escape 0x0C' 'M-C-l' +assert_key 0x0D 'Enter' -- 'Escape 0x0D' 'M-Enter' +assert_key 0x0E 'C-n' -- 'Escape 0x0E' 'M-C-n' +assert_key 0x0F 'C-o' -- 'Escape 0x0F' 'M-C-o' +assert_key 0x10 'C-p' -- 'Escape 0x10' 'M-C-p' +assert_key 0x11 'C-q' -- 'Escape 0x11' 'M-C-q' +assert_key 0x12 'C-r' -- 'Escape 0x12' 'M-C-r' +assert_key 0x13 'C-s' -- 'Escape 0x13' 'M-C-s' +assert_key 0x14 'C-t' -- 'Escape 0x14' 'M-C-t' +assert_key 0x15 'C-u' -- 'Escape 0x15' 'M-C-u' +assert_key 0x16 'C-v' -- 'Escape 0x16' 'M-C-v' +assert_key 0x17 'C-w' -- 'Escape 0x17' 'M-C-w' +assert_key 0x18 'C-x' -- 'Escape 0x18' 'M-C-x' +assert_key 0x19 'C-y' -- 'Escape 0x19' 'M-C-y' +assert_key 0x1A 'C-z' -- 'Escape 0x1A' 'M-C-z' +assert_key 0x1B 'Escape' -- 'Escape 0x1B' 'M-Escape' +assert_key 0x1C "C-\\" -- 'Escape 0x1C' "M-C-\\" +assert_key 0x1D 'C-]' -- 'Escape 0x1D' 'M-C-]' +assert_key 0x1E 'C-^' -- 'Escape 0x1E' 'M-C-^' +assert_key 0x1F 'C-_' -- 'Escape 0x1F' 'M-C-_' +assert_key 0x20 'Space' -- 'Escape 0x20' 'M-Space' +assert_key 0x21 '!' -- 'Escape 0x21' 'M-!' +assert_key 0x22 '"' -- 'Escape 0x22' 'M-"' +assert_key 0x23 '#' -- 'Escape 0x23'= 'M-#' +assert_key 0x24 '$' -- 'Escape 0x24'= 'M-$' +assert_key 0x25 '%' -- 'Escape 0x25'= 'M-%' +assert_key 0x26 '&' -- 'Escape 0x26'= 'M-&' +assert_key 0x27 "'" -- 'Escape 0x27' "M-'" +assert_key 0x28 '(' -- 'Escape 0x28' 'M-(' +assert_key 0x29 ')' -- 'Escape 0x29' 'M-)' +assert_key 0x2A '*' -- 'Escape 0x2A' 'M-*' +assert_key 0x2B '+' -- 'Escape 0x2B' 'M-+' +assert_key 0x2C ',' -- 'Escape 0x2C' 'M-,' +assert_key 0x2D '-' -- 'Escape 0x2D' 'M--' +assert_key 0x2E '.' -- 'Escape 0x2E' 'M-.' +assert_key 0x2F '/' -- 'Escape 0x2F' 'M-/' +assert_key 0x30 '0' -- 'Escape 0x30' 'M-0' +assert_key 0x31 '1' -- 'Escape 0x31' 'M-1' +assert_key 0x32 '2' -- 'Escape 0x32' 'M-2' +assert_key 0x33 '3' -- 'Escape 0x33' 'M-3' +assert_key 0x34 '4' -- 'Escape 0x34' 'M-4' +assert_key 0x35 '5' -- 'Escape 0x35' 'M-5' +assert_key 0x36 '6' -- 'Escape 0x36' 'M-6' +assert_key 0x37 '7' -- 'Escape 0x37' 'M-7' +assert_key 0x38 '8' -- 'Escape 0x38' 'M-8' +assert_key 0x39 '9' -- 'Escape 0x39' 'M-9' +assert_key 0x3A ':' -- 'Escape 0x3A' 'M-:' +assert_key 0x3B ';' -- 'Escape 0x3B' 'M-;' +assert_key 0x3C '<' -- 'Escape 0x3C' 'M-<' +assert_key 0x3D '=' -- 'Escape 0x3D' 'M-=' +assert_key 0x3E '>' -- 'Escape 0x3E' 'M->' +assert_key 0x3F '?' -- 'Escape 0x3F' 'M-?' +assert_key 0x40 '@' -- 'Escape 0x40' 'M-@' +assert_key 0x41 'A' -- 'Escape 0x41' 'M-A' +assert_key 0x42 'B' -- 'Escape 0x42' 'M-B' +assert_key 0x43 'C' -- 'Escape 0x43' 'M-C' +assert_key 0x44 'D' -- 'Escape 0x44' 'M-D' +assert_key 0x45 'E' -- 'Escape 0x45' 'M-E' +assert_key 0x46 'F' -- 'Escape 0x46' 'M-F' +assert_key 0x47 'G' -- 'Escape 0x47' 'M-G' +assert_key 0x48 'H' -- 'Escape 0x48' 'M-H' +assert_key 0x49 'I' -- 'Escape 0x49' 'M-I' +assert_key 0x4A 'J' -- 'Escape 0x4A' 'M-J' +assert_key 0x4B 'K' -- 'Escape 0x4B' 'M-K' +assert_key 0x4C 'L' -- 'Escape 0x4C' 'M-L' +assert_key 0x4D 'M' -- 'Escape 0x4D' 'M-M' +assert_key 0x4E 'N' -- 'Escape 0x4E' 'M-N' +assert_key 0x4F 'O' -- 'Escape 0x4F' 'M-O' +assert_key 0x50 'P' -- 'Escape 0x50' 'M-P' +assert_key 0x51 'Q' -- 'Escape 0x51' 'M-Q' +assert_key 0x52 'R' -- 'Escape 0x52' 'M-R' +assert_key 0x53 'S' -- 'Escape 0x53' 'M-S' +assert_key 0x54 'T' -- 'Escape 0x54' 'M-T' +assert_key 0x55 'U' -- 'Escape 0x55' 'M-U' +assert_key 0x56 'V' -- 'Escape 0x56' 'M-V' +assert_key 0x57 'W' -- 'Escape 0x57' 'M-W' +assert_key 0x58 'X' -- 'Escape 0x58' 'M-X' +assert_key 0x59 'Y' -- 'Escape 0x59' 'M-Y' +assert_key 0x5A 'Z' -- 'Escape 0x5A' 'M-Z' +assert_key 0x5B '[' -- 'Escape 0x5B' 'M-[' +assert_key 0x5C "\\" -- 'Escape 0x5C' "M-\\" +assert_key 0x5D ']' -- 'Escape 0x5D' 'M-]' +assert_key 0x5E '^' -- 'Escape 0x5E' 'M-^' +assert_key 0x5F '_' -- 'Escape 0x5F' 'M-_' +assert_key 0x60 '`' -- 'Escape 0x60' 'M-`' +assert_key 0x61 'a' -- 'Escape 0x61' 'M-a' +assert_key 0x62 'b' -- 'Escape 0x62' 'M-b' +assert_key 0x63 'c' -- 'Escape 0x63' 'M-c' +assert_key 0x64 'd' -- 'Escape 0x64' 'M-d' +assert_key 0x65 'e' -- 'Escape 0x65' 'M-e' +assert_key 0x66 'f' -- 'Escape 0x66' 'M-f' +assert_key 0x67 'g' -- 'Escape 0x67' 'M-g' +assert_key 0x68 'h' -- 'Escape 0x68' 'M-h' +assert_key 0x69 'i' -- 'Escape 0x69' 'M-i' +assert_key 0x6A 'j' -- 'Escape 0x6A' 'M-j' +assert_key 0x6B 'k' -- 'Escape 0x6B' 'M-k' +assert_key 0x6C 'l' -- 'Escape 0x6C' 'M-l' +assert_key 0x6D 'm' -- 'Escape 0x6D' 'M-m' +assert_key 0x6E 'n' -- 'Escape 0x6E' 'M-n' +assert_key 0x6F 'o' -- 'Escape 0x6F' 'M-o' +assert_key 0x70 'p' -- 'Escape 0x70' 'M-p' +assert_key 0x71 'q' -- 'Escape 0x71' 'M-q' +assert_key 0x72 'r' -- 'Escape 0x72' 'M-r' +assert_key 0x73 's' -- 'Escape 0x73' 'M-s' +assert_key 0x74 't' -- 'Escape 0x74' 'M-t' +assert_key 0x75 'u' -- 'Escape 0x75' 'M-u' +assert_key 0x76 'v' -- 'Escape 0x76' 'M-v' +assert_key 0x77 'w' -- 'Escape 0x77' 'M-w' +assert_key 0x78 'x' -- 'Escape 0x78' 'M-x' +assert_key 0x79 'y' -- 'Escape 0x79' 'M-y' +assert_key 0x7A 'z' -- 'Escape 0x7A' 'M-z' +assert_key 0x7B '{' -- 'Escape 0x7B' 'M-{' +assert_key 0x7C '|' -- 'Escape 0x7C' 'M-|' +assert_key 0x7D '}' -- 'Escape 0x7D' 'M-}' +assert_key 0x7E '~' -- 'Escape 0x7E' 'M-~' +assert_key 0x7F 'BSpace' -- 'Escape 0x7F' 'M-BSpace' + +# Numeric keypad +assert_key 'Escape OM' 'KPEnter' -- 'Escape Escape OM' 'M-KPEnter' +assert_key 'Escape Oj' 'KP*' -- 'Escape Escape Oj' 'M-KP*' +assert_key 'Escape Ok' 'KP+' -- 'Escape Escape Ok' 'M-KP+' +assert_key 'Escape Om' 'KP-' -- 'Escape Escape Om' 'M-KP-' +assert_key 'Escape On' 'KP.' -- 'Escape Escape On' 'M-KP.' +assert_key 'Escape Oo' 'KP/' -- 'Escape Escape Oo' 'M-KP/' +assert_key 'Escape Op' 'KP0' -- 'Escape Escape Op' 'M-KP0' +assert_key 'Escape Oq' 'KP1' -- 'Escape Escape Oq' 'M-KP1' +assert_key 'Escape Or' 'KP2' -- 'Escape Escape Or' 'M-KP2' +assert_key 'Escape Os' 'KP3' -- 'Escape Escape Os' 'M-KP3' +assert_key 'Escape Ot' 'KP4' -- 'Escape Escape Ot' 'M-KP4' +assert_key 'Escape Ou' 'KP5' -- 'Escape Escape Ou' 'M-KP5' +assert_key 'Escape Ov' 'KP6' -- 'Escape Escape Ov' 'M-KP6' +assert_key 'Escape Ow' 'KP7' -- 'Escape Escape Ow' 'M-KP7' +assert_key 'Escape Ox' 'KP8' -- 'Escape Escape Ox' 'M-KP8' +assert_key 'Escape Oy' 'KP9' -- 'Escape Escape Oy' 'M-KP9' + +# Arrow keys +assert_key 'Escape OA' 'Up' -- 'Escape Escape OA' 'M-Up' +assert_key 'Escape OB' 'Down' -- 'Escape Escape OB' 'M-Down' +assert_key 'Escape OC' 'Right' -- 'Escape Escape OC' 'M-Right' +assert_key 'Escape OD' 'Left' -- 'Escape Escape OD' 'M-Left' + +assert_key 'Escape [A' 'Up' -- 'Escape Escape [A' 'M-Up' +assert_key 'Escape [B' 'Down' -- 'Escape Escape [B' 'M-Down' +assert_key 'Escape [C' 'Right' -- 'Escape Escape [C' 'M-Right' +assert_key 'Escape [D' 'Left' -- 'Escape Escape [D' 'M-Left' + +# Other xterm keys +assert_key 'Escape OH' 'Home' -- 'Escape Escape OH' 'M-Home' +assert_key 'Escape OF' 'End' -- 'Escape Escape OF' 'M-End' + +assert_key 'Escape [H' 'Home' -- 'Escape Escape [H' 'M-Home' +assert_key 'Escape [F' 'End' -- 'Escape Escape [F' 'M-End' + +# rxvt arrow keys +assert_key 'Escape Oa' 'C-Up' +assert_key 'Escape Ob' 'C-Down' +assert_key 'Escape Oc' 'C-Right' +assert_key 'Escape Od' 'C-Left' +assert_key 'Escape [a' 'S-Up' +assert_key 'Escape [b' 'S-Down' +assert_key 'Escape [c' 'S-Right' +assert_key 'Escape [d' 'S-Left' + +# rxvt function keys +assert_key 'Escape [11~' 'F1' +assert_key 'Escape [12~' 'F2' +assert_key 'Escape [13~' 'F3' +assert_key 'Escape [14~' 'F4' +assert_key 'Escape [15~' 'F5' +assert_key 'Escape [17~' 'F6' +assert_key 'Escape [18~' 'F7' +assert_key 'Escape [19~' 'F8' +assert_key 'Escape [20~' 'F9' +assert_key 'Escape [21~' 'F10' +assert_key 'Escape [23~' 'F11' +assert_key 'Escape [24~' 'F12' + +# With TERM=screen, these will be seen as F11 and F12 +# assert_key 'Escape [23~' 'S-F1' +# assert_key 'Escape [24~' 'S-F2' +assert_key 'Escape [25~' 'S-F3' +assert_key 'Escape [26~' 'S-F4' +assert_key 'Escape [28~' 'S-F5' +assert_key 'Escape [29~' 'S-F6' +assert_key 'Escape [31~' 'S-F7' +assert_key 'Escape [32~' 'S-F8' +assert_key 'Escape [33~' 'S-F9' +assert_key 'Escape [34~' 'S-F10' +assert_key 'Escape [23$' 'S-F11' +assert_key 'Escape [24$' 'S-F12' + +assert_key 'Escape [11^' 'C-F1' +assert_key 'Escape [12^' 'C-F2' +assert_key 'Escape [13^' 'C-F3' +assert_key 'Escape [14^' 'C-F4' +assert_key 'Escape [15^' 'C-F5' +assert_key 'Escape [17^' 'C-F6' +assert_key 'Escape [18^' 'C-F7' +assert_key 'Escape [19^' 'C-F8' +assert_key 'Escape [20^' 'C-F9' +assert_key 'Escape [21^' 'C-F10' +assert_key 'Escape [23^' 'C-F11' +assert_key 'Escape [24^' 'C-F12' + +assert_key 'Escape [11@' 'C-S-F1' +assert_key 'Escape [12@' 'C-S-F2' +assert_key 'Escape [13@' 'C-S-F3' +assert_key 'Escape [14@' 'C-S-F4' +assert_key 'Escape [15@' 'C-S-F5' +assert_key 'Escape [17@' 'C-S-F6' +assert_key 'Escape [18@' 'C-S-F7' +assert_key 'Escape [19@' 'C-S-F8' +assert_key 'Escape [20@' 'C-S-F9' +assert_key 'Escape [21@' 'C-S-F10' +assert_key 'Escape [23@' 'C-S-F11' +assert_key 'Escape [24@' 'C-S-F12' + +# Focus tracking +assert_key 'Escape [I' 'FocusIn' +assert_key 'Escape [O' 'FocusOut' + +# Paste keys +assert_key 'Escape [200~' 'PasteStart' +assert_key 'Escape [201~' 'PasteEnd' + +assert_key 'Escape [Z' 'BTab' + +assert_extended_key () { + code=$1 + key_name=$2 + + assert_key "Escape [${code};5u" "C-$key_name" + assert_key "Escape [${code};7u" "M-C-$key_name" +} + +# Extended keys +# assert_extended_key 65 'A' +# assert_extended_key 66 'B' +# assert_extended_key 67 'C' +# assert_extended_key 68 'D' +# assert_extended_key 69 'E' +# assert_extended_key 70 'F' +# assert_extended_key 71 'G' +# assert_extended_key 72 'H' +# assert_extended_key 73 'I' +# assert_extended_key 74 'J' +# assert_extended_key 75 'K' +# assert_extended_key 76 'L' +# assert_extended_key 77 'M' +# assert_extended_key 78 'N' +# assert_extended_key 79 'O' +# assert_extended_key 80 'P' +# assert_extended_key 81 'Q' +# assert_extended_key 82 'R' +# assert_extended_key 83 'S' +# assert_extended_key 84 'T' +# assert_extended_key 85 'U' +# assert_extended_key 86 'V' +# assert_extended_key 87 'W' +# assert_extended_key 88 'X' +# assert_extended_key 89 'Y' +# assert_extended_key 90 'Z' +# assert_extended_key 123 '{' +# assert_extended_key 124 '|' +# assert_extended_key 125 '}' + +# assert_key 'Escape [105;5u' 'C-i' +# assert_key 'Escape [73;5u' 'C-I' + +# assert_key 'Escape [109;5u' 'C-m' +# assert_key 'Escape [77;5u' 'C-M' + +# assert_key 'Escape [91;5u' 'C-[' +assert_key 'Escape [123;5u' 'C-{' + +# assert_key 'Escape [64;5u' 'C-@' + +assert_key 'Escape [32;2u' 'S-Space' +# assert_key 'Escape [32;6u' 'C-S-Space' + +assert_key 'Escape [9;5u' 'C-Tab' +assert_key 'Escape [1;5Z' 'C-S-Tab' + +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null + +exit $exit_status diff --git a/screen-write.c b/screen-write.c index 458cb348..0718df87 100644 --- a/screen-write.c +++ b/screen-write.c @@ -34,7 +34,7 @@ static void screen_write_collect_flush(struct screen_write_ctx *, int, static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static const struct grid_cell *screen_write_combine(struct screen_write_ctx *, - const struct utf8_data *, u_int *); + const struct utf8_data *, u_int *, u_int *); struct screen_write_citem { u_int x; @@ -1905,13 +1905,13 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) */ if (ctx->flags & SCREEN_WRITE_ZWJ) { screen_write_collect_flush(ctx, 0, __func__); - screen_write_combine(ctx, &zwj, &xx); + screen_write_combine(ctx, &zwj, &xx, &cx); } if (width == 0 || (ctx->flags & SCREEN_WRITE_ZWJ)) { ctx->flags &= ~SCREEN_WRITE_ZWJ; screen_write_collect_flush(ctx, 0, __func__); - if ((gc = screen_write_combine(ctx, ud, &xx)) != NULL) { - cx = s->cx; cy = s->cy; + if ((gc = screen_write_combine(ctx, ud, &xx, &cx)) != NULL) { + cy = s->cy; screen_write_set_cursor(ctx, xx, s->cy); screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = gc; @@ -2038,16 +2038,19 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Combine a UTF-8 zero-width character onto the previous. */ static const struct grid_cell * screen_write_combine(struct screen_write_ctx *ctx, const struct utf8_data *ud, - u_int *xx) + u_int *xx, u_int *cx) { struct screen *s = ctx->s; struct grid *gd = s->grid; static struct grid_cell gc; - u_int n; + u_int n, width; /* Can't combine if at 0. */ - if (s->cx == 0) + if (s->cx == 0) { + *xx = 0; return (NULL); + } + *xx = s->cx; /* Empty data is out. */ if (ud->size == 0) @@ -2061,22 +2064,35 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct utf8_data *ud, } if (n > s->cx) return (NULL); - *xx = s->cx - n; /* Check there is enough space. */ if (gc.data.size + ud->size > sizeof gc.data.data) return (NULL); + (*xx) -= n; - log_debug("%s: %.*s onto %.*s at %u,%u", __func__, (int)ud->size, - ud->data, (int)gc.data.size, gc.data.data, *xx, s->cy); + log_debug("%s: %.*s onto %.*s at %u,%u (width %u)", __func__, + (int)ud->size, ud->data, (int)gc.data.size, gc.data.data, *xx, + s->cy, gc.data.width); /* Append the data. */ memcpy(gc.data.data + gc.data.size, ud->data, ud->size); gc.data.size += ud->size; + width = gc.data.width; + + /* If this is U+FE0F VARIATION SELECTOR-16, force the width to 2. */ + if (gc.data.width == 1 && + ud->size == 3 && + memcmp(ud->data, "\357\270\217", 3) == 0) { + grid_view_set_padding(gd, (*xx) + 1, s->cy); + gc.data.width = 2; + width += 2; + } /* Set the new cell. */ grid_view_set_cell(gd, *xx, s->cy, &gc); + *cx = (*xx) + width; + log_debug("%s: character at %u; cursor at %u", __func__, *xx, *cx); return (&gc); } diff --git a/server-client.c b/server-client.c index e86426a5..25ecab4e 100644 --- a/server-client.c +++ b/server-client.c @@ -42,6 +42,7 @@ static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); static void server_client_set_path(struct client *); static void server_client_reset_state(struct client *); +static int server_client_is_bracket_pasting(struct client *, key_code); static int server_client_assume_paste(struct session *); static void server_client_update_latest(struct client *); @@ -1754,6 +1755,25 @@ out: return (key); } +/* Is this a bracket paste key? */ +static int +server_client_is_bracket_pasting(struct client *c, key_code key) +{ + if (key == KEYC_PASTE_START) { + c->flags |= CLIENT_BRACKETPASTING; + log_debug("%s: bracket paste on", c->name); + return (1); + } + + if (key == KEYC_PASTE_END) { + c->flags &= ~CLIENT_BRACKETPASTING; + log_debug("%s: bracket paste off", c->name); + return (1); + } + + return !!(c->flags & CLIENT_BRACKETPASTING); +} + /* Is this fast enough to probably be a paste? */ static int server_client_assume_paste(struct session *s) @@ -1862,8 +1882,14 @@ server_client_key_callback(struct cmdq_item *item, void *data) if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse")) goto forward_key; + /* Forward if bracket pasting. */ + if (server_client_is_bracket_pasting(c, key)) + goto forward_key; + /* Treat everything as a regular key when pasting is detected. */ - if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) + if (!KEYC_IS_MOUSE(key) && + (~key & KEYC_SENT) && + server_client_assume_paste(s)) goto forward_key; /* @@ -3214,3 +3240,69 @@ server_client_remove_pane(struct window_pane *wp) } } } + +/* Print to a client. */ +void +server_client_print(struct client *c, int parse, struct evbuffer *evb) +{ + void *data = EVBUFFER_DATA(evb); + size_t size = EVBUFFER_LENGTH(evb); + struct window_pane *wp; + struct window_mode_entry *wme; + char *sanitized, *msg, *line; + + if (!parse) { + utf8_stravisx(&msg, data, size, + VIS_OCTAL|VIS_CSTYLE|VIS_NOSLASH); + log_debug("%s: %s", __func__, msg); + } else { + msg = EVBUFFER_DATA(evb); + if (msg[size - 1] != '\0') + evbuffer_add(evb, "", 1); + } + + if (c == NULL) + goto out; + + if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + if (~c->flags & CLIENT_UTF8) { + sanitized = utf8_sanitize(msg); + if (c->flags & CLIENT_CONTROL) + control_write(c, "%s", sanitized); + else + file_print(c, "%s\n", sanitized); + free(sanitized); + } else { + if (c->flags & CLIENT_CONTROL) + control_write(c, "%s", msg); + else + file_print(c, "%s\n", msg); + } + goto out; + } + + wp = server_client_get_pane(c); + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->mode != &window_view_mode) + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); + if (parse) { + do { + line = evbuffer_readln(evb, NULL, EVBUFFER_EOL_LF); + if (line != NULL) { + window_copy_add(wp, 1, "%s", line); + free(line); + } + } while (line != NULL); + + size = EVBUFFER_LENGTH(evb); + if (size != 0) { + line = EVBUFFER_DATA(evb); + window_copy_add(wp, 1, "%.*s", (int)size, line); + } + } else + window_copy_add(wp, 0, "%s", msg); + +out: + if (!parse) + free(msg); +} diff --git a/status.c b/status.c index b504bbbe..08952f58 100644 --- a/status.c +++ b/status.c @@ -1766,7 +1766,7 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size, else offset = 0; - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, NULL, status_prompt_menu_callback, spm) != 0) { menu_free(menu); free(spm); @@ -1859,7 +1859,7 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, else offset = 0; - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, NULL, status_prompt_menu_callback, spm) != 0) { menu_free(menu); free(spm); diff --git a/tmux-protocol.h b/tmux-protocol.h index 08422291..3cf00c09 100644 --- a/tmux-protocol.h +++ b/tmux-protocol.h @@ -66,7 +66,8 @@ enum msgtype { MSG_WRITE_OPEN, MSG_WRITE, MSG_WRITE_READY, - MSG_WRITE_CLOSE + MSG_WRITE_CLOSE, + MSG_READ_CANCEL }; /* @@ -92,6 +93,10 @@ struct msg_read_done { int error; }; +struct msg_read_cancel { + int stream; +}; + struct msg_write_open { int stream; int fd; diff --git a/tmux.1 b/tmux.1 index ef559af5..2009924f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -469,7 +469,8 @@ and .Ic confirm-before , parse their argument to create a new command which is inserted immediately after themselves. -This means that arguments can be parsed twice or more - once when the parent command (such as +This means that arguments can be parsed twice or more - once when the parent +command (such as .Ic if-shell ) is parsed and again when it parses and executes its command. Commands like @@ -545,7 +546,7 @@ for example in these .Xr sh 1 commands: .Bd -literal -offset indent -$ tmux neww\e\e; splitw +$ tmux neww\e; splitw .Ed .Pp Or: @@ -887,8 +888,8 @@ may consist entirely of the token .Ql {mouse} (alternative form .Ql = ) -to specify the session, window or pane where the most recent mouse event occurred -(see the +to specify the session, window or pane where the most recent mouse event +occurred (see the .Sx MOUSE SUPPORT section) or @@ -964,7 +965,7 @@ Will run directly without invoking the shell. .Pp .Ar command -.Op Ar arguments +.Op Ar argument ... refers to a .Nm command, either passed with the command and arguments separately, for example: @@ -1167,13 +1168,17 @@ session. .Tg lsc .It Xo Ic list-clients .Op Fl F Ar format +.Op Fl f Ar filter .Op Fl t Ar target-session .Xc .D1 Pq alias: Ic lsc List all clients attached to the server. -For the meaning of the .Fl F -flag, see the +specifies the format of each line and +.Fl f +a filter. +Only clients for which the filter is true are shown. +See the .Sx FORMATS section. If @@ -1460,7 +1465,7 @@ requests the clipboard from the client using the .Xr xterm 1 escape sequence. If -Ar target-pane +.Ar target-pane is given, the clipboard is sent (in encoded form), otherwise it is stored in a new paste buffer. .Pp @@ -1541,8 +1546,7 @@ show debugging information about jobs and terminals. .Tg source .It Xo Ic source-file .Op Fl Fnqv -.Ar path -.Ar ... +.Ar path ... .Xc .D1 Pq alias: Ic source Execute commands from one or more files specified by @@ -1574,7 +1578,8 @@ server, if not already running, without creating any sessions. .Pp Note that as by default the .Nm -server will exit with no sessions, this is only useful if a session is created in +server will exit with no sessions, this is only useful if a session is created +in .Pa ~/.tmux.conf , .Ic exit-empty is turned off, or another command is run as part of the same command sequence. @@ -1929,7 +1934,8 @@ bind PageUp copy-mode -eu .Ed .El .Pp -A number of preset arrangements of panes are available, these are called layouts. +A number of preset arrangements of panes are available, these are called +layouts. These may be selected with the .Ic select-layout command or cycled with @@ -1975,7 +1981,7 @@ For example: $ tmux list-windows 0: ksh [159x48] layout: bb62,159x48,0,0{79x48,0,0,79x48,80,0} -$ tmux select-layout bb62,159x48,0,0{79x48,0,0,79x48,80,0} +$ tmux select-layout 'bb62,159x48,0,0{79x48,0,0,79x48,80,0}' .Ed .Pp .Nm @@ -3123,7 +3129,7 @@ Commands related to key bindings are as follows: .Op Fl nr .Op Fl N Ar note .Op Fl T Ar key-table -.Ar key command Op Ar arguments +.Ar key command Op Ar argument ... .Xc .D1 Pq alias: Ic bind Bind key @@ -3215,13 +3221,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 ... +.Ar key ... .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 @@ -3230,6 +3237,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 @@ -3690,6 +3703,8 @@ Supports the overline SGR attribute. Supports the DECFRA rectangle fill escape sequence. .It RGB Supports RGB colour with the SGR escape sequences. +.It sixel +Supports SIXEL graphics. .It strikethrough Supports the strikethrough SGR escape sequence. .It sync @@ -4586,7 +4601,8 @@ hook and there are a number of hooks not associated with commands. .Pp Hooks are stored as array options, members of the array are executed in order when the hook is triggered. -Like options different hooks may be global or belong to a session, window or pane. +Like options different hooks may be global or belong to a session, window or +pane. Hooks may be configured with the .Ic set-hook or @@ -4769,7 +4785,8 @@ or .Ar target-pane in commands bound to mouse key bindings. It resolves to the window or pane over which the mouse event took place -(for example, the window in the status line over which button 1 was released for a +(for example, the window in the status line over which button 1 was released +for a .Ql MouseUp1Status binding, or the pane over which the wheel was scrolled for a .Ql WheelDownPane @@ -4909,13 +4926,16 @@ ignores case. For example: .Ql #{C/r:^Start} .Pp -Numeric operators may be performed by prefixing two comma-separated alternatives with an +Numeric operators may be performed by prefixing two comma-separated alternatives +with an .Ql e and an operator. An optional .Ql f -flag may be given after the operator to use floating point numbers, otherwise integers are used. -This may be followed by a number giving the number of decimal places to use for the result. +flag may be given after the operator to use floating point numbers, otherwise +integers are used. +This may be followed by a number giving the number of decimal places to use for +the result. The available operators are: addition .Ql + , @@ -5045,10 +5065,11 @@ but also expands .Xr strftime 3 specifiers. .Ql S:\& , -.Ql W:\& -or +.Ql W:\& , .Ql P:\& -will loop over each session, window or pane and insert the format once +or +.Ql L:\& +will loop over each session, window, pane or client and insert the format once for each. For windows and panes, two comma-separated formats may be given: the second is used for the current window or active pane. @@ -5075,7 +5096,8 @@ will substitute with .Ql bar throughout. -The first argument may be an extended regular expression and a final argument may be +The first argument may be an extended regular expression and a final argument +may be .Ql i to ignore case, for example .Ql s/a(.)/\e1x/i:\& @@ -5083,6 +5105,15 @@ would change .Ql abABab into .Ql bxBxbx . +A different delimiter character may also be used, to avoid collisions with +literal slashes in the pattern. +For example, +.Ql s|foo/|bar/|:\& +will substitute +.Ql foo/ +with +.Ql bar/ +throughout. .Pp In addition, the last line of a shell command's output may be inserted using .Ql #() . @@ -5093,10 +5124,10 @@ When constructing formats, .Nm does not wait for .Ql #() -commands to finish; instead, the previous result from running the same command is used, -or a placeholder if the command has not been run before. -If the command hasn't exited, the most recent line of output will be used, but the status -line will not be updated more than once a second. +commands to finish; instead, the previous result from running the same command +is used, or a placeholder if the command has not been run before. +If the command hasn't exited, the most recent line of output will be used, but +the status line will not be updated more than once a second. Commands are executed using .Pa /bin/sh and with the @@ -5397,8 +5428,8 @@ option: .Ic list=on marks the start of the list; .Ic list=focus -is the part of the list that should be kept in focus if the entire list won't fit -in the available space (typically the current window); +is the part of the list that should be kept in focus if the entire list won't +fit in the available space (typically the current window); .Ic list=left-marker and .Ic list=right-marker @@ -5801,13 +5832,13 @@ until it is dismissed. .Op Fl O .Op Fl c Ar target-client .Op Fl t Ar target-pane +.Op Fl S Ar starting-choice .Op Fl T Ar title .Op Fl x Ar position .Op Fl y Ar position .Ar name .Ar key -.Ar command -.Ar ... +.Ar command Op Ar argument ... .Xc .D1 Pq alias: Ic menu Display a menu on @@ -5831,6 +5862,9 @@ command should be omitted. .Fl T is a format for the menu title (see .Sx FORMATS ) . +.Fl S +sets the menu item selected by default, if the menu is not bound to a mouse key +binding. .Pp .Fl x and @@ -6545,8 +6579,8 @@ and matching .Em %end or .Em %error -have three arguments: an integer time (as seconds from epoch), command number and -flags (currently not used). +have three arguments: an integer time (as seconds from epoch), command number +and flags (currently not used). For example: .Bd -literal -offset indent %begin 1363006971 2 1 @@ -6596,11 +6630,17 @@ sent when the .Ar pause-after flag is set. .Ar age -is the time in milliseconds for which tmux had buffered the output before it was sent. +is the time in milliseconds for which tmux had buffered the output before it +was sent. Any subsequent arguments up until a single .Ql \&: are for future use and should be ignored. -.It Ic %layout-change Ar window-id Ar window-layout Ar window-visible-layout Ar window-flags +.It Xo Ic %layout-change +.Ar window-id +.Ar window-layout +.Ar window-visible-layout +.Ar window-flags +.Xc The layout of a window with ID .Ar window-id changed. @@ -6610,6 +6650,10 @@ The window's visible layout is .Ar window-visible-layout and the window flags are .Ar window-flags . +.It Ic %message Ar message +A message sent with the +.Ic display-message +command. .It Ic %output Ar pane-id Ar value A window pane produced output. .Ar value diff --git a/tmux.h b/tmux.h index 41958938..251d826d 100644 --- a/tmux.h +++ b/tmux.h @@ -133,13 +133,14 @@ struct winlink; #define KEYC_SHIFT 0x00400000000000ULL /* Key flag bits. */ -#define KEYC_LITERAL 0x01000000000000ULL -#define KEYC_KEYPAD 0x02000000000000ULL -#define KEYC_CURSOR 0x04000000000000ULL +#define KEYC_LITERAL 0x01000000000000ULL +#define KEYC_KEYPAD 0x02000000000000ULL +#define KEYC_CURSOR 0x04000000000000ULL #define KEYC_IMPLIED_META 0x08000000000000ULL #define KEYC_BUILD_MODIFIERS 0x10000000000000ULL -#define KEYC_VI 0x20000000000000ULL -#define KEYC_EXTENDED 0x40000000000000ULL +#define KEYC_VI 0x20000000000000ULL +#define KEYC_EXTENDED 0x40000000000000ULL +#define KEYC_SENT 0x80000000000000ULL /* Masks for key bits. */ #define KEYC_MASK_MODIFIERS 0x00f00000000000ULL @@ -1398,6 +1399,8 @@ struct tty { u_int osy; int mode; + int fg; + int bg; u_int rlower; u_int rupper; @@ -1425,9 +1428,14 @@ struct tty { #define TTY_OPENED 0x20 #define TTY_OSC52QUERY 0x40 #define TTY_BLOCK 0x80 -#define TTY_HAVEDA 0x100 +#define TTY_HAVEDA 0x100 /* Primary DA. */ #define TTY_HAVEXDA 0x200 #define TTY_SYNCING 0x400 +#define TTY_HAVEDA2 0x800 /* Secondary DA. */ +#define TTY_HAVEFG 0x1000 +#define TTY_HAVEBG 0x2000 +#define TTY_ALL_REQUEST_FLAGS \ + (TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVEFG|TTY_HAVEBG) int flags; struct tty_term *term; @@ -1823,6 +1831,7 @@ struct client { #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL #define CLIENT_WINDOWSIZECHANGED 0x400000000ULL #define CLIENT_CLIPBOARDBUFFER 0x800000000ULL +#define CLIENT_BRACKETPASTING 0x1000000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ @@ -2407,7 +2416,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 **); @@ -2570,6 +2579,7 @@ u_int cmdq_next(struct client *); struct cmdq_item *cmdq_running(struct client *); void cmdq_guard(struct cmdq_item *, const char *, int); void printflike(2, 3) cmdq_print(struct cmdq_item *, const char *, ...); +void cmdq_print_data(struct cmdq_item *, int, struct evbuffer *); void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...); /* cmd-wait-for.c */ @@ -2624,7 +2634,9 @@ void file_print_buffer(struct client *, void *, size_t); void printflike(2, 3) file_error(struct client *, const char *, ...); void file_write(struct client *, const char *, int, const void *, size_t, client_file_cb, void *); -void file_read(struct client *, const char *, client_file_cb, void *); +struct client_file *file_read(struct client *, const char *, client_file_cb, + void *); +void file_cancel(struct client_file *); void file_push(struct client_file *); int file_write_left(struct client_files *); void file_write_open(struct client_files *, struct tmuxpeer *, @@ -2636,6 +2648,7 @@ void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *, void file_write_ready(struct client_files *, struct imsg *); void file_read_data(struct client_files *, struct imsg *); void file_read_done(struct client_files *, struct imsg *); +void file_read_cancel(struct client_files *, struct imsg *); /* server.c */ extern struct tmuxproc *server_proc; @@ -2687,6 +2700,7 @@ struct client_window *server_client_add_client_window(struct client *, u_int); struct window_pane *server_client_get_pane(struct client *); void server_client_set_pane(struct client *, struct window_pane *); void server_client_remove_pane(struct window_pane *); +void server_client_print(struct client *, int, struct evbuffer *); /* server-fn.c */ void server_redraw_client(struct client *); @@ -2779,6 +2793,7 @@ int colour_fromstring(const char *s); int colour_256toRGB(int); int colour_256to16(int); int colour_byname(const char *); +int colour_parseX11(const char *); void colour_palette_init(struct colour_palette *); void colour_palette_clear(struct colour_palette *); void colour_palette_free(struct colour_palette *); @@ -3303,11 +3318,11 @@ void menu_add_item(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, struct cmd_find_state *); void menu_free(struct menu *); -struct menu_data *menu_prepare(struct menu *, int, struct cmdq_item *, u_int, - u_int, struct client *, struct cmd_find_state *, +struct menu_data *menu_prepare(struct menu *, int, int, struct cmdq_item *, + u_int, u_int, struct client *, struct cmd_find_state *, menu_choice_cb, void *); -int menu_display(struct menu *, int, struct cmdq_item *, u_int, - u_int, struct client *, struct cmd_find_state *, +int menu_display(struct menu *, int, int, struct cmdq_item *, + u_int, u_int, struct client *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); void menu_check_cb(struct client *, void *, u_int, u_int, u_int, diff --git a/tty-features.c b/tty-features.c index 8ed61e9a..dca40fad 100644 --- a/tty-features.c +++ b/tty-features.c @@ -345,6 +345,17 @@ static const struct tty_feature tty_feature_ignorefkeys = { 0 }; +/* Terminal has sixel capability. */ +static const char *const tty_feature_sixel_capabilities[] = { + "Sxl", + NULL +}; +static const struct tty_feature tty_feature_sixel = { + "sixel", + tty_feature_sixel_capabilities, + TERM_SIXEL +}; + /* Available terminal features. */ static const struct tty_feature *const tty_features[] = { &tty_feature_256, @@ -362,6 +373,7 @@ static const struct tty_feature *const tty_features[] = { &tty_feature_overline, &tty_feature_rectfill, &tty_feature_rgb, + &tty_feature_sixel, &tty_feature_strikethrough, &tty_feature_sync, &tty_feature_title, diff --git a/tty-keys.c b/tty-keys.c index 9e6f4134..03e91f96 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -55,8 +55,11 @@ static int tty_keys_clipboard(struct tty *, const char *, size_t, size_t *); static int tty_keys_device_attributes(struct tty *, const char *, size_t, size_t *); +static int tty_keys_device_attributes2(struct tty *, const char *, size_t, + size_t *); static int tty_keys_extended_device_attributes(struct tty *, const char *, size_t, size_t *); +static int tty_keys_colours(struct tty *, const char *, size_t, size_t *); /* A key tree entry. */ struct tty_key { @@ -684,7 +687,7 @@ tty_keys_next(struct tty *tty) goto partial_key; } - /* Is this a device attributes response? */ + /* Is this a primary device attributes response? */ switch (tty_keys_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ key = KEYC_UNKNOWN; @@ -695,6 +698,17 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this a secondary device attributes response? */ + switch (tty_keys_device_attributes2(tty, buf, len, &size)) { + case 0: /* yes */ + key = KEYC_UNKNOWN; + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + /* Is this an extended device attributes response? */ switch (tty_keys_extended_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ @@ -706,6 +720,17 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this a colours response? */ + switch (tty_keys_colours(tty, buf, len, &size)) { + case 0: /* yes */ + key = KEYC_UNKNOWN; + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + /* Is this a mouse key press? */ switch (tty_keys_mouse(tty, buf, len, &size, &m)) { case 0: /* yes */ @@ -1242,7 +1267,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) // } /* - * Handle secondary device attributes input. Returns 0 for success, -1 for + * Handle primary device attributes input. Returns 0 for success, -1 for * failure, 1 for partial. */ static int @@ -1250,17 +1275,15 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; + int *features = &c->term_features; u_int i, n = 0; - char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; + char tmp[128], *endptr, p[32] = { 0 }, *cp, *next; *size = 0; if (tty->flags & TTY_HAVEDA) return (-1); - /* - * First three bytes are always \033[>. Some older Terminal.app - * versions respond as for DA (\033[?) so accept and ignore that. - */ + /* First three bytes are always \033[?. */ if (buf[0] != '\033') return (-1); if (len == 1) @@ -1269,56 +1292,127 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, return (-1); if (len == 2) return (1); - if (buf[2] != '>' && buf[2] != '?') + if (buf[2] != '?') return (-1); if (len == 3) return (1); - /* Copy the rest up to a 'c'. */ - for (i = 0; i < (sizeof tmp) - 1; i++) { + /* Copy the rest up to a c. */ + for (i = 0; i < (sizeof tmp); i++) { if (3 + i == len) return (1); if (buf[3 + i] == 'c') break; tmp[i] = buf[3 + i]; } - if (i == (sizeof tmp) - 1) + if (i == (sizeof tmp)) return (-1); tmp[i] = '\0'; *size = 4 + i; - /* Ignore DA response. */ - if (buf[2] == '?') - return (0); - /* Convert all arguments to numbers. */ cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); if (*endptr != '\0') p[n] = 0; - n++; + if (++n == nitems(p)) + break; + } + + /* Add terminal features. */ + switch (p[0]) { + case 62: /* VT220 */ + case 63: /* VT320 */ + case 64: /* VT420 */ + for (i = 1; i < n; i++) { + log_debug("%s: DA feature: %d", c->name, p[i]); + if (p[i] == 4) + tty_add_features(features, "sixel", ","); + } + break; + } + log_debug("%s: received primary DA %.*s", c->name, (int)*size, buf); + + tty_update_features(tty); + tty->flags |= TTY_HAVEDA; + + return (0); +} + +/* + * Handle secondary device attributes input. Returns 0 for success, -1 for + * failure, 1 for partial. + */ +static int +tty_keys_device_attributes2(struct tty *tty, const char *buf, size_t len, + size_t *size) +{ + struct client *c = tty->client; + int *features = &c->term_features; + u_int i, n = 0; + char tmp[128], *endptr, p[32] = { 0 }, *cp, *next; + + *size = 0; + if (tty->flags & TTY_HAVEDA2) + return (-1); + + /* First three bytes are always \033[>. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != '[') + return (-1); + if (len == 2) + return (1); + if (buf[2] != '>') + return (-1); + if (len == 3) + return (1); + + /* Copy the rest up to a c. */ + for (i = 0; i < (sizeof tmp); i++) { + if (3 + i == len) + return (1); + if (buf[3 + i] == 'c') + break; + tmp[i] = buf[3 + i]; + } + if (i == (sizeof tmp)) + return (-1); + tmp[i] = '\0'; + *size = 4 + i; + + /* Convert all arguments to numbers. */ + cp = tmp; + while ((next = strsep(&cp, ";")) != NULL) { + p[n] = strtoul(next, &endptr, 10); + if (*endptr != '\0') + p[n] = 0; + if (++n == nitems(p)) + break; } /* Add terminal features. */ switch (p[0]) { case 41: /* VT420 */ - tty_add_features(&c->term_features, "margins,rectfill", ","); + tty_add_features(features, "margins,rectfill", ","); break; case 'M': /* mintty */ - tty_default_features(&c->term_features, "mintty", 0); + tty_default_features(features, "mintty", 0); break; case 'T': /* tmux */ - tty_default_features(&c->term_features, "tmux", 0); + tty_default_features(features, "tmux", 0); break; case 'U': /* rxvt-unicode */ - tty_default_features(&c->term_features, "rxvt-unicode", 0); + tty_default_features(features, "rxvt-unicode", 0); break; } log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf); tty_update_features(tty); - tty->flags |= TTY_HAVEDA; + tty->flags |= TTY_HAVEDA2; return (0); } @@ -1332,6 +1426,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; + int *features = &c->term_features; u_int i; char tmp[128]; @@ -1357,7 +1452,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, if (len == 4) return (1); - /* Copy the rest up to a '\033\\'. */ + /* Copy the rest up to \033\. */ for (i = 0; i < (sizeof tmp) - 1; i++) { if (4 + i == len) return (1); @@ -1372,13 +1467,13 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, /* Add terminal features. */ if (strncmp(tmp, "iTerm2 ", 7) == 0) - tty_default_features(&c->term_features, "iTerm2", 0); + tty_default_features(features, "iTerm2", 0); else if (strncmp(tmp, "tmux ", 5) == 0) - tty_default_features(&c->term_features, "tmux", 0); + tty_default_features(features, "tmux", 0); else if (strncmp(tmp, "XTerm(", 6) == 0) - tty_default_features(&c->term_features, "XTerm", 0); + tty_default_features(features, "XTerm", 0); else if (strncmp(tmp, "mintty ", 7) == 0) - tty_default_features(&c->term_features, "mintty", 0); + tty_default_features(features, "mintty", 0); log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); free(c->term_type); @@ -1389,3 +1484,73 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, return (0); } + +/* + * Handle foreground or background input. Returns 0 for success, -1 for + * failure, 1 for partial. + */ +static int +tty_keys_colours(struct tty *tty, const char *buf, size_t len, size_t *size) +{ + struct client *c = tty->client; + u_int i; + char tmp[128]; + int n; + + *size = 0; + if ((tty->flags & TTY_HAVEFG) && (tty->flags & TTY_HAVEBG)) + return (-1); + + /* First four bytes are always \033]1 and 0 or 1 and ;. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != ']') + return (-1); + if (len == 2) + return (1); + if (buf[2] != '1') + return (-1); + if (len == 3) + return (1); + if (buf[3] != '0' && buf[3] != '1') + return (-1); + if (len == 4) + return (1); + if (buf[4] != ';') + return (-1); + if (len == 5) + return (1); + + /* Copy the rest up to \033\ or \007. */ + for (i = 0; i < (sizeof tmp) - 1; i++) { + if (5 + i == len) + return (1); + if (buf[5 + i - 1] == '\033' && buf[5 + i] == '\\') + break; + if (buf[5 + i] == '\007') + break; + tmp[i] = buf[5 + i]; + } + if (i == (sizeof tmp) - 1) + return (-1); + if (tmp[i - 1] == '\033') + tmp[i - 1] = '\0'; + else + tmp[i] = '\0'; + *size = 6 + i; + + n = colour_parseX11(tmp); + if (n != -1 && buf[3] == '0') { + log_debug("%s: foreground is %s", c->name, colour_tostring(n)); + tty->fg = n; + tty->flags |= TTY_HAVEFG; + } else if (n != -1) { + log_debug("%s: background is %s", c->name, colour_tostring(n)); + tty->bg = n; + tty->flags |= TTY_HAVEBG; + } + + return (0); +} diff --git a/tty-term.c b/tty-term.c index c514030a..4e9b7799 100644 --- a/tty-term.c +++ b/tty-term.c @@ -457,6 +457,9 @@ tty_term_apply_overrides(struct tty_term *term) a = options_array_next(a); } + /* Log the SIXEL flag. */ + log_debug("SIXEL flag is %d", !!(term->flags & TERM_SIXEL)); + /* Update the RGB flag if the terminal has RGB colours. */ if (tty_term_has(term, TTYC_SETRGBF) && tty_term_has(term, TTYC_SETRGBB)) diff --git a/tty.c b/tty.c index 1fe367de..0428f4ae 100644 --- a/tty.c +++ b/tty.c @@ -108,6 +108,7 @@ tty_init(struct tty *tty, struct client *c) tty->cstyle = SCREEN_CURSOR_DEFAULT; tty->ccolour = -1; + tty->fg = tty->bg = -1; if (tcgetattr(c->fd, &tty->tio) != 0) return (-1); @@ -286,7 +287,6 @@ tty_open(struct tty *tty, char **cause) evtimer_set(&tty->timer, tty_timer_callback, tty); tty_start_tty(tty); - tty_keys_build(tty); return (0); @@ -299,9 +299,9 @@ tty_start_timer_callback(__unused int fd, __unused short events, void *data) struct client *c = tty->client; log_debug("%s: start timer fired", c->name); - if ((tty->flags & (TTY_HAVEDA|TTY_HAVEXDA)) == 0) + if ((tty->flags & (TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA)) == 0) tty_update_features(tty); - tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); + tty->flags |= TTY_ALL_REQUEST_FLAGS; } void @@ -341,6 +341,8 @@ tty_start_tty(struct tty *tty) tty_puts(tty, "\033[?1000l\033[?1002l\033[?1003l"); tty_puts(tty, "\033[?1006l\033[?1005l"); } + if (tty_term_has(tty->term, TTYC_ENBP)) + tty_putcode(tty, TTYC_ENBP); evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); @@ -363,12 +365,18 @@ tty_send_requests(struct tty *tty) return; if (tty->term->flags & TERM_VT100LIKE) { - if (~tty->flags & TTY_HAVEDA) + if (~tty->term->flags & TTY_HAVEDA) + tty_puts(tty, "\033[c"); + if (~tty->flags & TTY_HAVEDA2) tty_puts(tty, "\033[>c"); if (~tty->flags & TTY_HAVEXDA) tty_puts(tty, "\033[>q"); + if (~tty->flags & TTY_HAVEFG) + tty_puts(tty, "\033]10;?\033\\"); + if (~tty->flags & TTY_HAVEBG) + tty_puts(tty, "\033]11;?\033\\"); } else - tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); + tty->flags |= TTY_ALL_REQUEST_FLAGS; } void @@ -411,8 +419,6 @@ tty_stop_tty(struct tty *tty) else if (tty_term_has(tty->term, TTYC_SS)) tty_raw(tty, tty_term_string1(tty->term, TTYC_SS, 0)); } - if (tty->mode & MODE_BRACKETPASTE) - tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (tty->ccolour != -1) tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); @@ -421,6 +427,8 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, "\033[?1000l\033[?1002l\033[?1003l"); tty_raw(tty, "\033[?1006l\033[?1005l"); } + if (tty_term_has(tty->term, TTYC_DSBP)) + tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (tty->term->flags & TERM_VT100LIKE) tty_raw(tty, "\033[?7727l"); @@ -819,12 +827,6 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) else if (mode & MODE_MOUSE_STANDARD) tty_puts(tty, "\033[?1000h"); } - if (changed & MODE_BRACKETPASTE) { - if (mode & MODE_BRACKETPASTE) - tty_putcode(tty, TTYC_ENBP); - else - tty_putcode(tty, TTYC_DSBP); - } tty->mode = mode; } diff --git a/utf8.c b/utf8.c index df75a769..042ddf89 100644 --- a/utf8.c +++ b/utf8.c @@ -229,14 +229,23 @@ utf8_width(struct utf8_data *ud, int *width) case 0: return (UTF8_ERROR); } + log_debug("UTF-8 %.*s is %08X", (int)ud->size, ud->data, (u_int)wc); #ifdef HAVE_UTF8PROC *width = utf8proc_wcwidth(wc); + log_debug("utf8proc_wcwidth(%08X) returned %d", (u_int)wc, *width); #else *width = wcwidth(wc); + log_debug("wcwidth(%08X) returned %d", (u_int)wc, *width); + if (*width < 0) { + /* + * C1 control characters are nonprintable, so they are always + * zero width. + */ + *width = (wc >= 0x80 && wc <= 0x9f) ? 0 : 1; + } #endif if (*width >= 0 && *width <= 0xff) return (UTF8_DONE); - log_debug("UTF-8 %.*s, wcwidth() %d", (int)ud->size, ud->data, *width); return (UTF8_ERROR); } diff --git a/window.c b/window.c index fe184bb4..60a21a58 100644 --- a/window.c +++ b/window.c @@ -64,6 +64,7 @@ static u_int next_active_point; struct window_pane_input_data { struct cmdq_item *item; u_int wp; + struct client_file *file; }; static struct window_pane *window_pane_create(struct window *, u_int, u_int, @@ -1543,18 +1544,18 @@ window_pane_input_callback(struct client *c, __unused const char *path, size_t len = EVBUFFER_LENGTH(buffer); wp = window_pane_find_by_id(cdata->wp); - if (wp == NULL || closed || error != 0 || (c->flags & CLIENT_DEAD)) { - if (wp == NULL) + if (cdata->file != NULL && (wp == NULL || c->flags & CLIENT_DEAD)) { + if (wp == NULL) { + c->retval = 1; c->flags |= CLIENT_EXIT; - - evbuffer_drain(buffer, len); + } + file_cancel(cdata->file); + } else if (cdata->file == NULL || closed || error != 0) { cmdq_continue(cdata->item); - server_client_unref(c); free(cdata); - return; - } - input_parse_buffer(wp, buf, len); + } else + input_parse_buffer(wp, buf, len); evbuffer_drain(buffer, len); } @@ -1577,9 +1578,8 @@ window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, cdata = xmalloc(sizeof *cdata); cdata->item = item; cdata->wp = wp->id; - + cdata->file = file_read(c, "-", window_pane_input_callback, cdata); c->references++; - file_read(c, "-", window_pane_input_callback, cdata); return (0); }