From 1764f66b7d1ed0e494cfa8967c78ace8728ee86c Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 19 Dec 2019 09:22:33 +0000 Subject: [PATCH 01/40] When adding a list with multiple commands to the queue, the next item to insert after needs to be the last one added, not the first. Reported by Jason Kim in GitHub issue 2023. --- cfg.c | 8 ++++---- cmd-queue.c | 23 ++++++++++++++++------- cmd-source-file.c | 1 + key-bindings.c | 4 ++-- notify.c | 4 +--- tmux.h | 4 ++-- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/cfg.c b/cfg.c index 8509ad1b..92c87225 100644 --- a/cfg.c +++ b/cfg.c @@ -185,9 +185,9 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); if (item != NULL) - cmdq_insert_after(item, new_item0); + new_item0 = cmdq_insert_after(item, new_item0); else - cmdq_append(NULL, new_item0); + new_item0 = cmdq_append(NULL, new_item0); cmd_list_free(pr->cmdlist); if (new_item != NULL) @@ -231,9 +231,9 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path, new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); if (item != NULL) - cmdq_insert_after(item, new_item0); + new_item0 = cmdq_insert_after(item, new_item0); else - cmdq_append(NULL, new_item0); + new_item0 = cmdq_append(NULL, new_item0); cmd_list_free(pr->cmdlist); if (new_item != NULL) diff --git a/cmd-queue.c b/cmd-queue.c index 69e4f6b2..75b5d8f9 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -53,12 +53,16 @@ cmdq_get(struct client *c) } /* Append an item. */ -void +struct cmdq_item * cmdq_append(struct client *c, struct cmdq_item *item) { struct cmdq_list *queue = cmdq_get(c); struct cmdq_item *next; + TAILQ_FOREACH(next, queue, entry) { + log_debug("%s %s: queue %s (%u)", __func__, cmdq_name(c), + next->name, next->group); + } do { next = item->next; item->next = NULL; @@ -73,16 +77,21 @@ cmdq_append(struct client *c, struct cmdq_item *item) item = next; } while (item != NULL); + return (TAILQ_LAST(queue, cmdq_list)); } /* Insert an item. */ -void +struct cmdq_item * cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) { struct client *c = after->client; struct cmdq_list *queue = after->queue; struct cmdq_item *next; + TAILQ_FOREACH(next, queue, entry) { + log_debug("%s %s: queue %s (%u)", __func__, cmdq_name(c), + next->name, next->group); + } do { next = item->next; item->next = after->next; @@ -100,6 +109,7 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) after = item; item = next; } while (item != NULL); + return (after); } /* Insert a hook. */ @@ -143,11 +153,10 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item, new_item = cmdq_get_command(cmdlist, fs, NULL, CMDQ_NOHOOKS); cmdq_format(new_item, "hook", "%s", name); - if (item != NULL) { - cmdq_insert_after(item, new_item); - item = new_item; - } else - cmdq_append(NULL, new_item); + if (item != NULL) + item = cmdq_insert_after(item, new_item); + else + item = cmdq_append(NULL, new_item); a = options_array_next(a); } diff --git a/cmd-source-file.c b/cmd-source-file.c index 6af1a6d0..c34cdf41 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -114,6 +114,7 @@ cmd_source_file_done(struct client *c, const char *path, int error, static void cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path) { + log_debug("%s: %s", __func__, path); cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1, sizeof *cdata->files); cdata->files[cdata->nfiles++] = xstrdup(path); diff --git a/key-bindings.c b/key-bindings.c index d4fada6a..7cd834a2 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -520,8 +520,8 @@ key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, new_item->shared->flags |= CMDQ_SHARED_REPEAT; } if (item != NULL) - cmdq_insert_after(item, new_item); + new_item = cmdq_insert_after(item, new_item); else - cmdq_append(c, new_item); + new_item = cmdq_append(c, new_item); return (new_item); } diff --git a/notify.c b/notify.c index 3079f0eb..c91a4399 100644 --- a/notify.c +++ b/notify.c @@ -90,9 +90,7 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) new_item = cmdq_get_command(cmdlist, &fs, NULL, CMDQ_NOHOOKS); cmdq_format(new_item, "hook", "%s", ne->name); notify_hook_formats(new_item, s, w, ne->pane); - - cmdq_insert_after(item, new_item); - item = new_item; + item = cmdq_insert_after(item, new_item); a = options_array_next(a); } diff --git a/tmux.h b/tmux.h index 8b23bfae..72be101b 100644 --- a/tmux.h +++ b/tmux.h @@ -2134,8 +2134,8 @@ struct cmdq_item *cmdq_get_command(struct cmd_list *, struct cmd_find_state *, #define cmdq_get_callback(cb, data) cmdq_get_callback1(#cb, cb, data) struct cmdq_item *cmdq_get_callback1(const char *, cmdq_cb, void *); struct cmdq_item *cmdq_get_error(const char *); -void cmdq_insert_after(struct cmdq_item *, struct cmdq_item *); -void cmdq_append(struct client *, struct cmdq_item *); +struct cmdq_item *cmdq_insert_after(struct cmdq_item *, struct cmdq_item *); +struct cmdq_item *cmdq_append(struct client *, struct cmdq_item *); void cmdq_insert_hook(struct session *, struct cmdq_item *, struct cmd_find_state *, const char *, ...); void cmdq_continue(struct cmdq_item *); From 5cd00eda0b7f1775bd3c8ce9497c72ddfada9592 Mon Sep 17 00:00:00 2001 From: tim Date: Sat, 21 Dec 2019 17:30:48 +0000 Subject: [PATCH 02/40] Restore source-file -q behaviour, broken in r1.42; OK nicm@ --- cmd-source-file.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd-source-file.c b/cmd-source-file.c index c34cdf41..46dc6d94 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -125,7 +125,6 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; struct cmd_source_file_data *cdata; - int flags = 0; struct client *c = item->client; enum cmd_retval retval = CMD_RETURN_NORMAL; char *pattern, *cwd; @@ -161,7 +160,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) if ((result = glob(pattern, 0, NULL, &g)) != 0) { if (result != GLOB_NOMATCH || - (~flags & CMD_PARSE_QUIET)) { + (~cdata->flags & CMD_PARSE_QUIET)) { if (result == GLOB_NOMATCH) error = strerror(ENOENT); else if (result == GLOB_NOSPACE) From 07e37479c214023f5d0107414a6e5f414d37197a Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 24 Dec 2019 09:57:11 +0000 Subject: [PATCH 03/40] Fix name of option, GitHub issue 2030. --- tmux.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index 56ebeb87..ce882193 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4360,7 +4360,7 @@ interface, for example .Ic status-style for the status line. In addition, embedded styles may be specified in format options, such as -.Ic status-left-format , +.Ic status-left , by enclosing them in .Ql #[ and From 817d199cbb6720f1848452824f7b93cdc1a24111 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 26 Dec 2019 11:04:58 +0000 Subject: [PATCH 04/40] Add a number of new formats to inspect what sessions and clients a window is present or active in. From Tyler Culp in GitHub issue 2034. --- format.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ session.c | 15 +++- tmux.1 | 10 +++ tmux.h | 1 + 4 files changed, 238 insertions(+), 1 deletion(-) diff --git a/format.c b/format.c index e74de9c1..8ee4fa61 100644 --- a/format.c +++ b/format.c @@ -456,6 +456,35 @@ format_cb_pid(__unused struct format_tree *ft, struct format_entry *fe) xasprintf(&fe->value, "%ld", (long)getpid()); } +/* Callback for session_attached_list. */ +static void +format_cb_session_attached_list(struct format_tree *ft, struct format_entry *fe) +{ + struct session *s = ft->s; + struct client *loop; + struct evbuffer *buffer; + int size; + + if (s == NULL) + return; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session == s) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + /* Callback for session_alerts. */ static void format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe) @@ -528,6 +557,128 @@ format_cb_window_stack_index(struct format_tree *ft, struct format_entry *fe) fe->value = xstrdup("0"); } +/* Callback for window_linked_sessions_list. */ +static void +format_cb_window_linked_sessions_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct winlink *wl; + struct evbuffer *buffer; + int size; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", wl->session->name); + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for window_active_sessions. */ +static void +format_cb_window_active_sessions(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct winlink *wl; + u_int n = 0; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session->curw == wl) + n++; + } + + xasprintf(&fe->value, "%u", n); +} + +/* Callback for window_active_sessions_list. */ +static void +format_cb_window_active_sessions_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct winlink *wl; + struct evbuffer *buffer; + int size; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session->curw == wl) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", wl->session->name); + } + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for window_active_clients. */ +static void +format_cb_window_active_clients(struct format_tree *ft, struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct client *loop; + struct session *client_session; + u_int n = 0; + + TAILQ_FOREACH(loop, &clients, entry) { + client_session = loop->session; + if (client_session == NULL) + continue; + + if (w == client_session->curw->window) + n++; + } + + xasprintf(&fe->value, "%u", n); +} + +/* Callback for window_active_clients_list. */ +static void +format_cb_window_active_clients_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct window *w = ft->wl->window; + struct client *loop; + struct session *client_session; + struct evbuffer *buffer; + int size; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(loop, &clients, entry) { + client_session = loop->session; + if (client_session == NULL) + continue; + + if (w == client_session->curw->window) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + /* Callback for window_layout. */ static void format_cb_window_layout(struct format_tree *ft, struct format_entry *fe) @@ -662,11 +813,52 @@ format_cb_session_group_list(struct format_tree *ft, struct format_entry *fe) buffer = evbuffer_new(); if (buffer == NULL) fatalx("out of memory"); + TAILQ_FOREACH(loop, &sg->sessions, gentry) { if (EVBUFFER_LENGTH(buffer) > 0) evbuffer_add(buffer, ",", 1); evbuffer_add_printf(buffer, "%s", loop->name); } + + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for session_group_attached_list. */ +static void +format_cb_session_group_attached_list(struct format_tree *ft, + struct format_entry *fe) +{ + struct session *s = ft->s, *client_session, *session_loop; + struct session_group *sg; + struct client *loop; + struct evbuffer *buffer; + int size; + + if (s == NULL) + return; + sg = session_group_contains(s); + if (sg == NULL) + return; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + + TAILQ_FOREACH(loop, &clients, entry) { + client_session = loop->session; + if (client_session == NULL) + continue; + TAILQ_FOREACH(session_loop, &sg->sessions, gentry) { + if (session_loop == client_session){ + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + } + } + if ((size = EVBUFFER_LENGTH(buffer)) != 0) xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); @@ -2125,8 +2317,14 @@ format_defaults_session(struct format_tree *ft, struct session *s) format_add(ft, "session_group", "%s", sg->name); format_add(ft, "session_group_size", "%u", session_group_count (sg)); + format_add(ft, "session_group_attached", "%u", + session_group_attached_count (sg)); + format_add(ft, "session_group_many_attached", "%u", + session_group_attached_count (sg) > 1); format_add_cb(ft, "session_group_list", format_cb_session_group_list); + format_add_cb(ft, "session_group_attached_list", + format_cb_session_group_attached_list); } format_add_tv(ft, "session_created", &s->creation_time); @@ -2135,6 +2333,8 @@ format_defaults_session(struct format_tree *ft, struct session *s) format_add(ft, "session_attached", "%u", s->attached); format_add(ft, "session_many_attached", "%d", s->attached > 1); + format_add_cb(ft, "session_attached_list", + format_cb_session_attached_list); format_add_cb(ft, "session_alerts", format_cb_session_alerts); format_add_cb(ft, "session_stack", format_cb_session_stack); @@ -2249,6 +2449,14 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) format_add_cb(ft, "window_stack_index", format_cb_window_stack_index); format_add(ft, "window_flags", "%s", window_printable_flags(wl)); format_add(ft, "window_active", "%d", wl == s->curw); + format_add_cb(ft, "window_active_sessions", + format_cb_window_active_sessions); + format_add_cb(ft, "window_active_sessions_list", + format_cb_window_active_sessions_list); + format_add_cb(ft, "window_active_clients", + format_cb_window_active_clients); + format_add_cb(ft, "window_active_clients_list", + format_cb_window_active_clients_list); format_add(ft, "window_start_flag", "%d", !!(wl == RB_MIN(winlinks, &s->windows))); @@ -2269,6 +2477,11 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) format_add(ft, "window_last_flag", "%d", !!(wl == TAILQ_FIRST(&s->lastw))); format_add(ft, "window_linked", "%d", session_is_linked(s, wl->window)); + + format_add_cb(ft, "window_linked_sessions_list", + format_cb_window_linked_sessions_list); + format_add(ft, "window_linked_sessions", "%u", + wl->window->references); } /* Set default format keys for a window pane. */ diff --git a/session.c b/session.c index b1f49e41..be9c8e07 100644 --- a/session.c +++ b/session.c @@ -569,7 +569,20 @@ session_group_count(struct session_group *sg) n = 0; TAILQ_FOREACH(s, &sg->sessions, gentry) - n++; + n++; + return (n); +} + +/* Count number of clients attached to sessions in session group. */ +u_int +session_group_attached_count(struct session_group *sg) +{ + struct session *s; + u_int n; + + n = 0; + TAILQ_FOREACH(s, &sg->sessions, gentry) + n += s->attached; return (n); } diff --git a/tmux.1 b/tmux.1 index ce882193..ef288e5a 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4310,10 +4310,14 @@ The following variables are available, where appropriate: .It Li "session_activity" Ta "" Ta "Time of session last activity" .It Li "session_alerts" Ta "" Ta "List of window indexes with alerts" .It Li "session_attached" Ta "" Ta "Number of clients session is attached to" +.It Li "session_attached_list" Ta "" Ta "List of clients session is attached to" .It Li "session_created" Ta "" Ta "Time session created" .It Li "session_format" Ta "" Ta "1 if format is for a session" .It Li "session_group" Ta "" Ta "Name of session group" +.It Li "session_group_attached" Ta "" Ta "Number of clients sessions in group are attached to" +.It Li "session_group_attached_list" Ta "" Ta "List of clients sessions in group are attached to" .It Li "session_group_list" Ta "" Ta "List of sessions in group" +.It Li "session_group_many_attached" Ta "" Ta "1 if multiple clients attached to sessions in group" .It Li "session_group_size" Ta "" Ta "Size of session group" .It Li "session_grouped" Ta "" Ta "1 if session in a group" .It Li "session_id" Ta "" Ta "Unique session ID" @@ -4325,6 +4329,10 @@ The following variables are available, where appropriate: .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "start_time" Ta "" Ta "Server start time" .It Li "window_active" Ta "" Ta "1 if window active" +.It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window" +.It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window" +.It Li "window_active_sessions" Ta "" Ta "Number of sessions on which this window is active" +.It Li "window_active_sessions_list" Ta "" Ta "List of sessions on which this window is active" .It Li "window_activity" Ta "" Ta "Time of window last activity" .It Li "window_activity_flag" Ta "" Ta "1 if window has activity" .It Li "window_bell_flag" Ta "" Ta "1 if window has bell" @@ -4340,6 +4348,8 @@ The following variables are available, where appropriate: .It Li "window_last_flag" Ta "" Ta "1 if window is the last used" .It Li "window_layout" Ta "" Ta "Window layout description, ignoring zoomed window panes" .It Li "window_linked" Ta "" Ta "1 if window is linked across sessions" +.It Li "window_linked_sessions" Ta "" Ta "Number of sessions this window is linked to" +.It Li "window_linked_sessions_list" Ta "" Ta "List of sessions this window is linked to" .It Li "window_marked_flag" Ta "" Ta "1 if window contains the marked pane" .It Li "window_name" Ta "#W" Ta "Name of window" .It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client" diff --git a/tmux.h b/tmux.h index 72be101b..2315f643 100644 --- a/tmux.h +++ b/tmux.h @@ -2689,6 +2689,7 @@ void session_group_add(struct session_group *, struct session *); void session_group_synchronize_to(struct session *); void session_group_synchronize_from(struct session *); u_int session_group_count(struct session_group *); +u_int session_group_attached_count(struct session_group *); void session_renumber_windows(struct session *); /* utf8.c */ From 88ee5b1a73c33c396580a1eb0a0605de98a79fa3 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 26 Dec 2019 14:48:29 +0000 Subject: [PATCH 05/40] Pass correct value into iterator callback for time formats. --- format.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/format.c b/format.c index 8ee4fa61..eaff6115 100644 --- a/format.c +++ b/format.c @@ -1150,12 +1150,12 @@ format_each(struct format_tree *ft, void (*cb)(const char *, const char *, void *), void *arg) { struct format_entry *fe; - static char s[64]; + char s[64]; RB_FOREACH(fe, format_entry_tree, &ft->tree) { if (fe->t != 0) { xsnprintf(s, sizeof s, "%lld", (long long)fe->t); - cb(fe->key, fe->value, s); + cb(fe->key, s, arg); } else { if (fe->value == NULL && fe->cb != NULL) { fe->cb(ft, fe); @@ -1198,8 +1198,7 @@ format_add(struct format_tree *ft, const char *key, const char *fmt, ...) static void format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv) { - struct format_entry *fe; - struct format_entry *fe_now; + struct format_entry *fe, *fe_now; fe = xmalloc(sizeof *fe); fe->key = xstrdup(key); From 4ea07716de541aea5b8118ac0cfc4bc3bd30cb80 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 27 Dec 2019 18:42:49 +0000 Subject: [PATCH 06/40] Support regex search in copy mode, from Anindya Mukherjee in GitHub issue 2038. --- window-copy.c | 397 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 359 insertions(+), 38 deletions(-) diff --git a/window-copy.c b/window-copy.c index 4787fb27..6e80daad 100644 --- a/window-copy.c +++ b/window-copy.c @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -57,18 +58,29 @@ static int window_copy_search_lr(struct grid *, struct grid *, u_int *, u_int, u_int, u_int, int); static int window_copy_search_rl(struct grid *, struct grid *, u_int *, u_int, u_int, u_int, int); +static int window_copy_search_lr_regex(struct grid *, struct grid *, + u_int *, u_int *, u_int, u_int, u_int, int); +static int window_copy_search_rl_regex(struct grid *, struct grid *, + u_int *, u_int *, u_int, u_int, u_int, int); +static int window_copy_last_regex(struct grid *gd, u_int py, u_int first, + u_int last, u_int len, u_int *ppx, u_int *psx, + const char *buf, const regex_t *preg, int eflags); +static char *window_copy_stringify(struct grid *, u_int, u_int, u_int, + char *, u_int *); +static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *, u_int *, + const char *str); static int window_copy_search_marks(struct window_mode_entry *, - struct screen *); + struct screen *, int); static void window_copy_clear_marks(struct window_mode_entry *); static void window_copy_move_left(struct screen *, u_int *, u_int *, int); static void window_copy_move_right(struct screen *, u_int *, u_int *, int); static int window_copy_is_lowercase(const char *); static int window_copy_search_jump(struct window_mode_entry *, struct grid *, struct grid *, u_int, u_int, u_int, int, int, - int); -static int window_copy_search(struct window_mode_entry *, int); -static int window_copy_search_up(struct window_mode_entry *); -static int window_copy_search_down(struct window_mode_entry *); + int, int); +static int window_copy_search(struct window_mode_entry *, int, int); +static int window_copy_search_up(struct window_mode_entry *, int); +static int window_copy_search_down(struct window_mode_entry *, int); static void window_copy_goto_line(struct window_mode_entry *, const char *); static void window_copy_update_cursor(struct window_mode_entry *, u_int, u_int); @@ -622,7 +634,7 @@ window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy) screen_write_stop(&ctx); if (search) - window_copy_search_marks(wme, NULL); + window_copy_search_marks(wme, NULL, 1); data->searchx = data->cx; data->searchy = data->cy; data->searcho = data->oy; @@ -1458,10 +1470,10 @@ window_copy_cmd_search_again(struct window_copy_cmd_state *cs) if (data->searchtype == WINDOW_COPY_SEARCHUP) { for (; np != 0; np--) - window_copy_search_up(wme); + window_copy_search_up(wme, 1); } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { for (; np != 0; np--) - window_copy_search_down(wme); + window_copy_search_down(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1475,10 +1487,10 @@ window_copy_cmd_search_reverse(struct window_copy_cmd_state *cs) if (data->searchtype == WINDOW_COPY_SEARCHUP) { for (; np != 0; np--) - window_copy_search_down(wme); + window_copy_search_down(wme, 1); } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { for (; np != 0; np--) - window_copy_search_up(wme); + window_copy_search_up(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1696,7 +1708,7 @@ window_copy_cmd_search_backward(struct window_copy_cmd_state *cs) if (data->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHUP; for (; np != 0; np--) - window_copy_search_up(wme); + window_copy_search_up(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1731,7 +1743,7 @@ window_copy_cmd_search_forward(struct window_copy_cmd_state *cs) if (data->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHDOWN; for (; np != 0; np--) - window_copy_search_down(wme); + window_copy_search_down(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1767,7 +1779,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHUP; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_up(wme)) { + if (!window_copy_search_up(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1776,7 +1788,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHDOWN; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_down(wme)) { + if (!window_copy_search_down(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1816,7 +1828,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHDOWN; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_down(wme)) { + if (!window_copy_search_down(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1825,7 +1837,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) data->searchtype = WINDOW_COPY_SEARCHUP; free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_up(wme)) { + if (!window_copy_search_up(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -2152,6 +2164,297 @@ window_copy_search_rl(struct grid *gd, return (0); } +static int +window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, + u_int *ppx, u_int *psx, u_int py, u_int first, u_int last, int cis) +{ + int cflags = REG_EXTENDED, eflags = 0; + u_int endline, foundx, foundy, len, pywrap, size = 1; + u_int ssize = 1; + char *buf, *sbuf; + regex_t reg; + regmatch_t regmatch; + struct grid_line *gl; + + /* + * This can happen during search if the last match was the last + * character on a line. + */ + if (first >= last) + return (0); + + sbuf = xmalloc(ssize); + sbuf[0] = '\0'; + sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); + if (sbuf == NULL) + return (0); + + /* Set flags for regex search. */ + if (cis) + cflags |= REG_ICASE; + if (regcomp(®, sbuf, cflags) != 0) { + free(sbuf); + return (0); + } + if (first != 0) + eflags |= REG_NOTBOL; + + /* Need to look at the entire string. */ + buf = xmalloc(size); + buf[0] = '\0'; + buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size); + len = gd->sx - first; + endline = gd->hsize + gd->sy - 1; + pywrap = py; + while (buf != NULL && pywrap <= endline) { + gl = grid_get_line(gd, pywrap); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + pywrap++; + buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size); + len += gd->sx; + } + + if (regexec(®, buf, 1, ®match, eflags) == 0) { + foundx = first; + foundy = py; + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + regmatch.rm_so); + if (foundy == py && foundx < last) { + *ppx = foundx; + len -= foundx - first; + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + regmatch.rm_eo); + *psx = foundx; + while (foundy > py) { + *psx += gd->sx; + foundy--; + } + *psx -= *ppx; + regfree(®); + free(sbuf); + free(buf); + return (1); + } + } + + regfree(®); + free(sbuf); + free(buf); + *ppx = 0; + *psx = 0; + return (0); +} + +static int +window_copy_search_rl_regex(struct grid *gd, struct grid *sgd, + u_int *ppx, u_int *psx, u_int py, u_int first, u_int last, int cis) +{ + int cflags = REG_EXTENDED, eflags = 0; + u_int endline, len, pywrap, size = 1, ssize = 1; + char *buf, *sbuf; + regex_t reg; + struct grid_line *gl; + + sbuf = xmalloc(ssize); + sbuf[0] = '\0'; + sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); + if (sbuf == NULL) + return (0); + + /* Set flags for regex search. */ + if (cis) + cflags |= REG_ICASE; + if (regcomp(®, sbuf, cflags) != 0) { + free(sbuf); + return (0); + } + if (first != 0) + eflags |= REG_NOTBOL; + + /* Need to look at the entire string. */ + buf = xmalloc(size); + buf[0] = '\0'; + buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size); + len = gd->sx - first; + endline = gd->hsize + gd->sy - 1; + pywrap = py; + while (buf != NULL && (pywrap <= endline)) { + gl = grid_get_line(gd, pywrap); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + pywrap++; + buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size); + len += gd->sx; + } + + if (window_copy_last_regex(gd, py, first, last, len, ppx, psx, buf, + ®, eflags)) + { + regfree(®); + free(sbuf); + free(buf); + return (1); + } + + regfree(®); + free(sbuf); + free(buf); + *ppx = 0; + *psx = 0; + return (0); +} + +/* Find last match in given range. */ +static int +window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last, + u_int len, u_int *ppx, u_int *psx, const char *buf, const regex_t *preg, + int eflags) +{ + u_int foundx, foundy, oldx, px = 0, savepx, savesx = 0; + regmatch_t regmatch; + + foundx = first; + foundy = py; + oldx = first; + while (regexec(preg, buf + px, 1, ®match, eflags) == 0) { + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + px + regmatch.rm_so); + if (foundy > py || foundx >= last) + break; + len -= foundx - oldx; + savepx = foundx; + window_copy_cstrtocellpos(gd, len, &foundx, &foundy, + buf + px + regmatch.rm_eo); + if (foundy > py || foundx >= last) { + *ppx = savepx; + *psx = foundx; + while (foundy > py) { + *psx += gd->sx; + foundy--; + } + *psx -= *ppx; + return (1); + } else { + savesx = foundx - savepx; + len -= savesx; + oldx = foundx; + } + px += regmatch.rm_eo; + } + + if (savesx > 0) { + *ppx = savepx; + *psx = savesx; + return (1); + } else { + *ppx = 0; + *psx = 0; + return (0); + } +} + +/* Stringify line and append to input buffer. Caller frees. */ +static char * +window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last, + char *buf, u_int *size) +{ + u_int ax, bx, newsize; + struct grid_cell gc; + + bx = *size - 1; + newsize = *size; + for (ax = first; ax < last; ax++) { + grid_get_cell(gd, ax, py, &gc); + newsize += gc.data.size; + buf = xrealloc(buf, newsize); + memcpy(buf + bx, gc.data.data, gc.data.size); + bx += gc.data.size; + } + + buf[newsize - 1] = '\0'; + *size = newsize; + return (buf); +} + +/* Map start of C string containing UTF-8 data to grid cell position. */ +static void +window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy, + const char *str) +{ + u_int cell, ccell, px, pywrap; + int match; + const char *cstr; + char *celldata, **cells; + struct grid_cell gc; + + /* Set up staggered array of cell contents. This speeds up search. */ + cells = xreallocarray(NULL, ncells, sizeof cells[0]); + + /* Populate the array of cell data. */ + cell = 0; + px = *ppx; + pywrap = *ppy; + while (cell < ncells) { + grid_get_cell(gd, px, pywrap, &gc); + celldata = xmalloc(gc.data.size + 1); + memcpy(celldata, gc.data.data, gc.data.size); + celldata[gc.data.size] = '\0'; + cells[cell] = celldata; + cell++; + px = (px + 1) % gd->sx; + if (px == 0) + pywrap++; + } + + /* Locate starting cell. */ + cell = 0; + while (cell < ncells) { + ccell = cell; + cstr = str; + match = 1; + while (ccell < ncells) { + /* Anchor found to the end. */ + if (*cstr == '\0') { + match = 0; + break; + } + + celldata = cells[ccell]; + while (*celldata != '\0' && *cstr != '\0') { + if (*celldata++ != *cstr++) { + match = 0; + break; + } + } + + if (!match) + break; + ccell++; + } + + if (match) + break; + cell++; + } + + /* If not found this will be one past the end. */ + px = *ppx + cell; + pywrap = *ppy; + while (px >= gd->sx) { + px -= gd->sx; + pywrap++; + } + + *ppx = px; + *ppy = pywrap; + + /* Free cell data. */ + for (cell = 0; cell < ncells; cell++) + free(cells[cell]); + free(cells); +} + static void window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag) { @@ -2206,24 +2509,31 @@ window_copy_is_lowercase(const char *ptr) static int window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap, - int direction) + int direction, int regex) { - u_int i, px; - int found; + u_int i, px, sx; + int found = 0; - found = 0; if (direction) { for (i = fy; i <= endline; i++) { - found = window_copy_search_lr(gd, sgd, &px, i, fx, - gd->sx, cis); + if (regex) + found = window_copy_search_lr_regex(gd, sgd, + &px, &sx, i, fx, gd->sx, cis); + else + found = window_copy_search_lr(gd, sgd, + &px, i, fx, gd->sx, cis); if (found) break; fx = 0; } } else { for (i = fy + 1; endline < i; i--) { - found = window_copy_search_rl(gd, sgd, &px, i - 1, 0, - fx + 1, cis); + if (regex) + found = window_copy_search_rl_regex(gd, sgd, + &px, &sx, i - 1, 0, fx + 1, cis); + else + found = window_copy_search_rl(gd, sgd, + &px, i - 1, 0, fx + 1, cis); if (found) { i--; break; @@ -2240,7 +2550,7 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, return (window_copy_search_jump(wme, gd, sgd, direction ? 0 : gd->sx - 1, direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0, - direction)); + direction, regex)); } return (0); } @@ -2250,7 +2560,7 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, * down. */ static int -window_copy_search(struct window_mode_entry *wme, int direction) +window_copy_search(struct window_mode_entry *wme, int direction, int regex) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; @@ -2281,10 +2591,11 @@ window_copy_search(struct window_mode_entry *wme, int direction) window_copy_move_left(s, &fx, &fy, wrapflag); endline = 0; } - found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis, - wrapflag, direction); - if (window_copy_search_marks(wme, &ss)) + found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis, + wrapflag, direction, regex); + + if (window_copy_search_marks(wme, &ss, regex)) window_copy_redraw_screen(wme); screen_free(&ss); @@ -2292,7 +2603,8 @@ window_copy_search(struct window_mode_entry *wme, int direction) } static int -window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp) +window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, + int regex) { struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing, ss; @@ -2320,10 +2632,19 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp) for (py = 0; py < gd->hsize + gd->sy; py++) { px = 0; for (;;) { - found = window_copy_search_lr(gd, ssp->grid, &px, py, - px, gd->sx, cis); - if (!found) - break; + if (regex) { + found = window_copy_search_lr_regex(gd, + ssp->grid, &px, &width, py, px, + gd->sx, cis); + if (!found) + break; + } + else { + found = window_copy_search_lr(gd, ssp->grid, + &px, py, px, gd->sx, cis); + if (!found) + break; + } nfound++; if (px == data->cx && py == gd->hsize + data->cy - data->oy) @@ -2357,15 +2678,15 @@ window_copy_clear_marks(struct window_mode_entry *wme) } static int -window_copy_search_up(struct window_mode_entry *wme) +window_copy_search_up(struct window_mode_entry *wme, int regex) { - return (window_copy_search(wme, 0)); + return (window_copy_search(wme, 0, regex)); } static int -window_copy_search_down(struct window_mode_entry *wme) +window_copy_search_down(struct window_mode_entry *wme, int regex) { - return (window_copy_search(wme, 1)); + return (window_copy_search(wme, 1, regex)); } static void From 206d878127b4ba4aedb5255b03d71272fa06bcab Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 Dec 2019 21:24:55 +0000 Subject: [PATCH 07/40] Do not let readonly clients limit the size, suggested by Max Barraclough in GitHub issue 2042. --- tmux.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tmux.h b/tmux.h index 2315f643..0a0fc1b5 100644 --- a/tmux.h +++ b/tmux.h @@ -1599,7 +1599,8 @@ struct client { #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ - CLIENT_DETACHING) + CLIENT_DETACHING| \ + CLIENT_READONLY) int flags; struct key_table *keytable; From 9cc603cbad33878dc0d988d3266a1589e74d7303 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jan 2020 21:51:33 +0000 Subject: [PATCH 08/40] Fix format expansion in window names, reported by Suraj N Kurapati. --- spawn.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spawn.c b/spawn.c index 6cf7c73b..75995221 100644 --- a/spawn.c +++ b/spawn.c @@ -80,6 +80,8 @@ spawn_log(const char *from, struct spawn_context *sc) struct winlink * spawn_window(struct spawn_context *sc, char **cause) { + struct cmdq_item *item = sc->item; + struct client *c = item->client; struct session *s = sc->s; struct window *w; struct window_pane *wp; @@ -182,7 +184,8 @@ spawn_window(struct spawn_context *sc, char **cause) /* Set the name of the new window. */ if (~sc->flags & SPAWN_RESPAWN) { if (sc->name != NULL) { - w->name = xstrdup(sc->name); + w->name = format_single(item, sc->name, c, s, NULL, + NULL); options_set_number(w->options, "automatic-rename", 0); } else w->name = xstrdup(default_window_name(w)); From ac85a3e0d37bc75a9ca0416898636a544af8eeb4 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 1 Jan 2020 22:12:05 +0000 Subject: [PATCH 09/40] Document client exit messages. --- tmux.1 | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/tmux.1 b/tmux.1 index ef288e5a..1e8c13af 100644 --- a/tmux.1 +++ b/tmux.1 @@ -933,7 +933,9 @@ If is specified, any other clients attached to the session are detached. If .Fl x -is given, send SIGHUP to the parent process of the client as well as +is given, send +.Dv SIGHUP +to the parent process of the client as well as detaching the client, typically causing it to exit. .Fl r signifies the client is read-only (only keys bound to the @@ -984,7 +986,9 @@ option kills all but the client given with .Fl t . If .Fl P -is given, send SIGHUP to the parent process of the client, typically causing it +is given, send +.Dv SIGHUP +to the parent process of the client, typically causing it to exit. With .Fl E , @@ -5148,6 +5152,37 @@ channel are made to wait until the channel is unlocked with .Ic wait-for .Fl U . .El +.Sh EXIT MESSAGES +When a +.Nm +client detaches, it prints a message. +This may be one of: +.Bl -tag -width Ds +.It [detached (from session ...)] +The client was detached normally. +.It [detached and SIGHUP] +The client was detached and its parent sent the +.Dv SIGHUP +signal (for example with +.Ic detach-client +.Fl P ) . +.It [lost tty] +The client's +.Xr tty 4 +or +.Xr pty 4 +was unexpectedly destroyed. +.It [terminated] +The client was killed with +.Dv SIGTERM . +.It [exited] +The server exited when it had no sessions. +.It [server exited] +The server exited when it received +.Dv SIGTERM . +.It [server exited unexpectedly] +The server crashed or otherwise exited without telling the client the reason. +.El .Sh TERMINFO EXTENSIONS .Nm understands some unofficial extensions to From a770a3bf7ea8370b850b19a50f2d4839b44c4a1f Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 2 Jan 2020 13:44:17 +0000 Subject: [PATCH 10/40] Add CMD_FIND_DEFAULT_MARKED to join-pane like move-pane, from davidegirardi in GitHub issue 2046. --- cmd-join-pane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 5344f3ec..94f3ae13 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -53,7 +53,7 @@ const struct cmd_entry cmd_move_pane_entry = { .args = { "bdhvp:l:s:t:", 0, 0 }, .usage = "[-bdhv] [-p percentage|-l size] " CMD_SRCDST_PANE_USAGE, - .source = { 's', CMD_FIND_PANE, 0 }, + .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, From 1870cc70ef6eded1ac3a809124ec21de1b98dbd9 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 4 Jan 2020 18:01:56 +0000 Subject: [PATCH 11/40] Add ~ to quoted characters for %%%, reported by tb@. --- cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd.c b/cmd.c index 163db73b..03a17ab1 100644 --- a/cmd.c +++ b/cmd.c @@ -661,7 +661,7 @@ char * cmd_template_replace(const char *template, const char *s, int idx) { char ch, *buf; - const char *ptr, *cp, quote[] = "\"\\$;"; + const char *ptr, *cp, quote[] = "\"\\$;~"; int replaced, quoted; size_t len; From 7c6c66cc3cc40b663fc2ba8573f26a6aa4cb291e Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 5 Jan 2020 12:51:43 +0000 Subject: [PATCH 12/40] Send errors to stdout in control mode so they don't get reordered with other output, reported by George Nachman in GitHub issue 2048. --- cmd-queue.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd-queue.c b/cmd-queue.c index 75b5d8f9..59c7a35c 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -551,7 +551,10 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) msg = utf8_sanitize(tmp); free(tmp); } - file_error(c, "%s\n", msg); + if (c->flags & CLIENT_CONTROL) + file_print(c, "%s\n", msg); + else + file_error(c, "%s\n", msg); c->retval = 1; } else { *msg = toupper((u_char) *msg); From 73b8c2ef3ca011fd7e7e46549c9640e9e123d59a Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 5 Jan 2020 20:39:25 +0000 Subject: [PATCH 13/40] Common function to free key bindings. --- key-bindings.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index 7cd834a2..94110a49 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -85,6 +85,14 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) return (0); } +static void +key_bindings_free(struct key_table *table, struct key_binding *bd) +{ + RB_REMOVE(key_bindings, &table->key_bindings, bd); + cmd_list_free(bd->cmdlist); + free(bd); +} + struct key_table * key_bindings_get_table(const char *name, int create) { @@ -126,11 +134,8 @@ key_bindings_unref_table(struct key_table *table) if (--table->references != 0) return; - RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); - cmd_list_free(bd->cmdlist); - free(bd); - } + RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) + key_bindings_free(table, bd); free((void *)table->name); free(table); @@ -162,17 +167,13 @@ key_bindings_add(const char *name, key_code key, int repeat, struct cmd_list *cmdlist) { struct key_table *table; - struct key_binding bd_find, *bd; + struct key_binding *bd; table = key_bindings_get_table(name, 1); - bd_find.key = (key & ~KEYC_XTERM); - bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); - if (bd != NULL) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); - cmd_list_free(bd->cmdlist); - free(bd); - } + bd = key_bindings_get(table, key & ~KEYC_XTERM); + if (bd != NULL) + key_bindings_free(table, bd); bd = xcalloc(1, sizeof *bd); bd->key = key; @@ -187,20 +188,16 @@ void key_bindings_remove(const char *name, key_code key) { struct key_table *table; - struct key_binding bd_find, *bd; + struct key_binding *bd; table = key_bindings_get_table(name, 0); if (table == NULL) return; - bd_find.key = (key & ~KEYC_XTERM); - bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); + bd = key_bindings_get(table, key & ~KEYC_XTERM); if (bd == NULL) return; - - RB_REMOVE(key_bindings, &table->key_bindings, bd); - cmd_list_free(bd->cmdlist); - free(bd); + key_bindings_free(table, bd); if (RB_EMPTY(&table->key_bindings)) { RB_REMOVE(key_tables, &key_tables, table); From 6628e542b5a0fabbd02800e68d19aecaf530fece Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 8 Jan 2020 06:38:55 +0000 Subject: [PATCH 14/40] Add -Z to default switch-client command in tree mode, matches previous behaviour. --- window-tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/window-tree.c b/window-tree.c index e0aa4314..d163dd9e 100644 --- a/window-tree.c +++ b/window-tree.c @@ -33,7 +33,7 @@ static void window_tree_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); -#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -t '%%'" +#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -Zt '%%'" #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ From 36eb16ce7db2db71b510964a2e453e6b349b03f0 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 8 Jan 2020 14:40:52 +0000 Subject: [PATCH 15/40] Do not hang in format_trim_* on invalid UTF-8 characters. --- format-draw.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/format-draw.c b/format-draw.c index 6cced9fd..bb16e0dd 100644 --- a/format-draw.c +++ b/format-draw.c @@ -849,8 +849,10 @@ format_trim_left(const char *expanded, u_int limit) out += ud.size; } width += ud.width; - } else + } else { cp -= ud.have; + cp++; + } } else if (*cp > 0x1f && *cp < 0x7f) { if (width + 1 <= limit) *out++ = *cp; @@ -896,8 +898,10 @@ format_trim_right(const char *expanded, u_int limit) out += ud.size; } width += ud.width; - } else + } else { cp -= ud.have; + cp++; + } } else if (*cp > 0x1f && *cp < 0x7f) { if (width >= skip) *out++ = *cp; From deb734c7f61c36e18ccc2e4fdab4f06a90575fc9 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 12 Jan 2020 20:20:20 +0000 Subject: [PATCH 16/40] Loop over all DA features, don't skip the first. --- tty-keys.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tty-keys.c b/tty-keys.c index 6be40d0e..1d97b7d2 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1038,7 +1038,7 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); - if (*endptr != '\0' && *endptr != ';') + if (*endptr != '\0') p[n] = 0; n++; } @@ -1068,7 +1068,7 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, type = TTY_VT520; break; } - for (i = 2; i < n; i++) + for (i = 1; i < n; i++) log_debug("%s: DA feature: %d", c->name, p[i]); tty_set_type(tty, type); From 193e637de050e3757698812e9a87b869c87def6c Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 12 Jan 2020 21:07:07 +0000 Subject: [PATCH 17/40] The terminal type was never as much use as I expected so remove it in favour of a couple of flags for the features used (DECSLRM and DECFRA). Also rename the flag for no xenl to be more obvious while here. --- format.c | 3 --- tmux.1 | 1 - tmux.h | 25 ++++--------------------- tty-keys.c | 40 +++++++++------------------------------- tty-term.c | 2 +- tty.c | 23 +++++++++++------------ 6 files changed, 25 insertions(+), 69 deletions(-) diff --git a/format.c b/format.c index eaff6115..f5e7b2ce 100644 --- a/format.c +++ b/format.c @@ -2346,7 +2346,6 @@ format_defaults_client(struct format_tree *ft, struct client *c) struct session *s; const char *name; struct tty *tty = &c->tty; - const char *types[] = TTY_TYPES; if (ft->s == NULL) ft->s = c->session; @@ -2364,8 +2363,6 @@ format_defaults_client(struct format_tree *ft, struct client *c) if (tty->term_name != NULL) format_add(ft, "client_termname", "%s", tty->term_name); - if (tty->term_name != NULL) - format_add(ft, "client_termtype", "%s", types[tty->term_type]); format_add_tv(ft, "client_created", &c->creation_time); format_add_tv(ft, "client_activity", &c->activity_time); diff --git a/tmux.1 b/tmux.1 index 1e8c13af..9c9cfb39 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4227,7 +4227,6 @@ The following variables are available, where appropriate: .It Li "client_readonly" Ta "" Ta "1 if client is readonly" .It Li "client_session" Ta "" Ta "Name of the client's session" .It Li "client_termname" Ta "" Ta "Terminal name of client" -.It Li "client_termtype" Ta "" Ta "Terminal type of client" .It Li "client_tty" Ta "" Ta "Pseudo terminal of client" .It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8" .It Li "client_width" Ta "" Ta "Width of client" diff --git a/tmux.h b/tmux.h index 0a0fc1b5..7c7dcc99 100644 --- a/tmux.h +++ b/tmux.h @@ -1168,7 +1168,9 @@ struct tty_term { struct tty_code *codes; #define TERM_256COLOURS 0x1 -#define TERM_EARLYWRAP 0x2 +#define TERM_NOXENL 0x2 +#define TERM_DECSLRM 0x4 +#define TERM_DECFRA 0x8 int flags; LIST_ENTRY(tty_term) entry; @@ -1230,16 +1232,6 @@ struct tty { struct tty_term *term; char *term_name; int term_flags; - enum { - TTY_VT100, - TTY_VT101, - TTY_VT102, - TTY_VT220, - TTY_VT320, - TTY_VT420, - TTY_VT520, - TTY_UNKNOWN - } term_type; u_int mouse_last_x; u_int mouse_last_y; @@ -1253,15 +1245,6 @@ struct tty { struct event key_timer; struct tty_key *key_tree; }; -#define TTY_TYPES \ - { "VT100", \ - "VT101", \ - "VT102", \ - "VT220", \ - "VT320", \ - "VT420", \ - "VT520", \ - "Unknown" } /* TTY command context. */ struct tty_ctx { @@ -1992,7 +1975,7 @@ void tty_draw_line(struct tty *, struct window_pane *, struct screen *, int tty_open(struct tty *, char **); void tty_close(struct tty *); void tty_free(struct tty *); -void tty_set_type(struct tty *, int); +void tty_set_flags(struct tty *, int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); diff --git a/tty-keys.c b/tty-keys.c index 1d97b7d2..4644340c 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1001,11 +1001,10 @@ static int tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { - struct client *c = tty->client; - u_int i, n = 0; - char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; - static const char *types[] = TTY_TYPES; - int type; + struct client *c = tty->client; + u_int i, n = 0; + char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; + int flags = 0; *size = 0; @@ -1043,36 +1042,15 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, n++; } - /* Store terminal type. */ - type = TTY_UNKNOWN; + /* Set terminal flags. */ switch (p[0]) { - case 1: - if (p[1] == 2) - type = TTY_VT100; - else if (p[1] == 0) - type = TTY_VT101; - break; - case 6: - type = TTY_VT102; - break; - case 62: - type = TTY_VT220; - break; - case 63: - type = TTY_VT320; - break; - case 64: - type = TTY_VT420; - break; - case 65: - type = TTY_VT520; + case 64: /* VT420 */ + flags |= (TERM_DECFRA|TERM_DECSLRM); break; } for (i = 1; i < n; i++) log_debug("%s: DA feature: %d", c->name, p[i]); - tty_set_type(tty, type); - - log_debug("%s: received DA %.*s (%s)", c->name, (int)*size, buf, - types[type]); + log_debug("%s: received DA %.*s", c->name, (int)*size, buf); + tty_set_flags(tty, flags); return (0); } diff --git a/tty-term.c b/tty-term.c index 52468faf..164ab2f1 100644 --- a/tty-term.c +++ b/tty-term.c @@ -538,7 +538,7 @@ tty_term_find(char *name, int fd, char **cause) * do the best possible. */ if (!tty_term_flag(term, TTYC_XENL)) - term->flags |= TERM_EARLYWRAP; + term->flags |= TERM_NOXENL; /* Generate ACS table. If none is present, use nearest ASCII. */ memset(term->acs, 0, sizeof term->acs); diff --git a/tty.c b/tty.c index 2d7acb17..c9843e90 100644 --- a/tty.c +++ b/tty.c @@ -74,7 +74,7 @@ static void tty_default_attributes(struct tty *, struct window_pane *, u_int); #define tty_use_margin(tty) \ - ((tty)->term_type == TTY_VT420) + ((tty->term->flags|tty->term_flags) & TERM_DECSLRM) #define tty_pane_full_width(tty, ctx) \ ((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx) @@ -115,9 +115,7 @@ tty_init(struct tty *tty, struct client *c, int fd, char *term) tty->ccolour = xstrdup(""); tty->flags = 0; - tty->term_flags = 0; - tty->term_type = TTY_UNKNOWN; return (0); } @@ -438,9 +436,9 @@ tty_free(struct tty *tty) } void -tty_set_type(struct tty *tty, int type) +tty_set_flags(struct tty *tty, int flags) { - tty->term_type = type; + tty->term_flags |= flags; if (tty_use_margin(tty)) tty_puts(tty, "\033[?69h"); /* DECLRMM */ @@ -543,7 +541,7 @@ tty_putc(struct tty *tty, u_char ch) { const char *acs; - if ((tty->term->flags & TERM_EARLYWRAP) && + if ((tty->term->flags & TERM_NOXENL) && ch >= 0x20 && ch != 0x7f && tty->cy == tty->sy - 1 && tty->cx + 1 >= tty->sx) @@ -569,7 +567,7 @@ tty_putc(struct tty *tty, u_char ch) * where we think it should be after a line wrap - this * means it works on sensible terminals as well. */ - if (tty->term->flags & TERM_EARLYWRAP) + if (tty->term->flags & TERM_NOXENL) tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx); } else tty->cx++; @@ -579,7 +577,7 @@ tty_putc(struct tty *tty, u_char ch) void tty_putn(struct tty *tty, const void *buf, size_t len, u_int width) { - if ((tty->term->flags & TERM_EARLYWRAP) && + if ((tty->term->flags & TERM_NOXENL) && tty->cy == tty->sy - 1 && tty->cx + len >= tty->sx) len = tty->sx - tty->cx - 1; @@ -1129,7 +1127,8 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, * background colour isn't default (because it doesn't work * after SGR 0). */ - if (tty->term_type == TTY_VT420 && !COLOUR_DEFAULT(bg)) { + if (((tty->term->flags|tty->term_flags) & TERM_DECFRA) && + !COLOUR_DEFAULT(bg)) { xsnprintf(tmp, sizeof tmp, "\033[32;%u;%u;%u;%u$x", py + 1, px + 1, py + ny, px + nx); tty_puts(tty, tmp); @@ -1824,7 +1823,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) ctx->xoff + ctx->ocx + ctx->num > ctx->ox + ctx->sx)) { if (!ctx->wrapped || !tty_pane_full_width(tty, ctx) || - (tty->term->flags & TERM_EARLYWRAP) || + (tty->term->flags & TERM_NOXENL) || ctx->xoff + ctx->ocx != 0 || ctx->yoff + ctx->ocy != tty->cy + 1 || tty->cx < tty->sx || @@ -1873,7 +1872,7 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) const struct grid_cell *gcp; /* Skip last character if terminal is stupid. */ - if ((tty->term->flags & TERM_EARLYWRAP) && + if ((tty->term->flags & TERM_NOXENL) && tty->cy == tty->sy - 1 && tty->cx == tty->sx - 1) return; @@ -2032,7 +2031,7 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx, { if (!ctx->wrapped || !tty_pane_full_width(tty, ctx) || - (tty->term->flags & TERM_EARLYWRAP) || + (tty->term->flags & TERM_NOXENL) || ctx->xoff + cx != 0 || ctx->yoff + cy != tty->cy + 1 || tty->cx < tty->sx || From 381333c4a9fd521bee8a0287e648f0c6aeb96a0b Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 12 Jan 2020 22:00:20 +0000 Subject: [PATCH 18/40] Detect iTerm2 and enable DECSLRM. --- tty-keys.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tty.c | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tty-keys.c b/tty-keys.c index 4644340c..5054c424 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -52,6 +52,8 @@ 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_status_report(struct tty *, const char *, + size_t, size_t *); /* Default raw keys. */ struct tty_default_key_raw { @@ -607,6 +609,17 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this a device status report response? */ + switch (tty_keys_device_status_report(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 */ @@ -1054,3 +1067,47 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, tty_set_flags(tty, flags); return (0); } + +/* + * Handle device status report input. Returns 0 for success, -1 for failure, 1 + * for partial. + */ +static int +tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, + size_t *size) +{ + struct client *c = tty->client; + u_int i; + char tmp[64]; + int flags = 0; + + *size = 0; + + /* 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); + + /* Copy the rest up to a 'n'. */ + for (i = 0; i < (sizeof tmp) - 1 && buf[2 + i] != 'n'; i++) { + if (2 + i == len) + return (1); + tmp[i] = buf[2 + i]; + } + if (i == (sizeof tmp) - 1) + return (-1); + tmp[i] = '\0'; + *size = 3 + i; + + /* Set terminal flags. */ + if (strncmp(tmp, "ITERM2 ", 7) == 0) + flags |= TERM_DECSLRM; + log_debug("%s: received DSR %.*s", c->name, (int)*size, buf); + tty_set_flags(tty, flags); + return (0); +} diff --git a/tty.c b/tty.c index c9843e90..2d4b4cc0 100644 --- a/tty.c +++ b/tty.c @@ -327,7 +327,7 @@ tty_start_tty(struct tty *tty) tty->flags |= TTY_FOCUS; tty_puts(tty, "\033[?1004h"); } - tty_puts(tty, "\033[c"); + tty_puts(tty, "\033[c\033[1337n"); } tty->flags |= TTY_STARTED; From 04eee2410df4a85005c9562db13a1fec93d7c2e4 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jan 2020 07:51:54 +0000 Subject: [PATCH 19/40] Treat plausible but invalid keys (like C-BSpace) as literal like any other unrecognised string passed to send-keys. Reported by Anthony Sottile in GitHub issue 2049. --- cmd-send-keys.c | 14 +++++++++++--- input-keys.c | 17 +++++++++-------- tmux.h | 4 ++-- window.c | 14 ++++++++------ 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/cmd-send-keys.c b/cmd-send-keys.c index ddbab6f7..cc04a73f 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -60,6 +60,9 @@ static struct cmdq_item * cmd_send_keys_inject_key(struct client *c, struct cmd_find_state *fs, struct cmdq_item *item, key_code key) { + struct session *s = fs->s; + struct winlink *wl = fs->wl; + struct window_pane *wp = fs->wp; struct window_mode_entry *wme; struct key_table *table; struct key_binding *bd; @@ -68,7 +71,8 @@ cmd_send_keys_inject_key(struct client *c, struct cmd_find_state *fs, if (wme == NULL || wme->mode->key_table == NULL) { if (options_get_number(fs->wp->window->options, "xterm-keys")) key |= KEYC_XTERM; - window_pane_key(fs->wp, item->client, fs->s, fs->wl, key, NULL); + if (window_pane_key(wp, item->client, s, wl, key, NULL) != 0) + return (NULL); return (item); } table = key_bindings_get_table(wme->mode->key_table(wme), 1); @@ -87,6 +91,7 @@ cmd_send_keys_inject_string(struct client *c, struct cmd_find_state *fs, struct cmdq_item *item, struct args *args, int i) { const char *s = args->argv[i]; + struct cmdq_item *new_item; struct utf8_data *ud, *uc; wchar_t wc; key_code key; @@ -104,8 +109,11 @@ cmd_send_keys_inject_string(struct client *c, struct cmd_find_state *fs, literal = args_has(args, 'l'); if (!literal) { key = key_string_lookup_string(s); - if (key != KEYC_NONE && key != KEYC_UNKNOWN) - return (cmd_send_keys_inject_key(c, fs, item, key)); + if (key != KEYC_NONE && key != KEYC_UNKNOWN) { + new_item = cmd_send_keys_inject_key(c, fs, item, key); + if (new_item != NULL) + return (new_item); + } literal = 1; } if (literal) { diff --git a/input-keys.c b/input-keys.c index 01f9ad9d..5c7e8b1f 100644 --- a/input-keys.c +++ b/input-keys.c @@ -150,7 +150,7 @@ input_split2(u_int c, u_char *dst) } /* Translate a key code into an output key sequence. */ -void +int input_key(struct window_pane *wp, key_code key, struct mouse_event *m) { const struct input_key_ent *ike; @@ -167,14 +167,14 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) if (KEYC_IS_MOUSE(key)) { if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id) input_key_mouse(wp, m); - return; + return (0); } /* Literal keys go as themselves (can't be more than eight bits). */ if (key & KEYC_LITERAL) { ud.data[0] = (u_char)key; bufferevent_write(wp->event, &ud.data[0], 1); - return; + return (0); } /* Is this backspace? */ @@ -195,15 +195,15 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) bufferevent_write(wp->event, "\033", 1); ud.data[0] = justkey; bufferevent_write(wp->event, &ud.data[0], 1); - return; + return (0); } if (justkey > 0x7f && justkey < KEYC_BASE) { if (utf8_split(justkey, &ud) != UTF8_DONE) - return; + return (-1); if (key & KEYC_ESCAPE) bufferevent_write(wp->event, "\033", 1); bufferevent_write(wp->event, ud.data, ud.size); - return; + return (0); } /* @@ -214,7 +214,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) if ((out = xterm_keys_lookup(key)) != NULL) { bufferevent_write(wp->event, out, strlen(out)); free(out); - return; + return (0); } } key &= ~KEYC_XTERM; @@ -237,7 +237,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) } if (i == nitems(input_keys)) { log_debug("key 0x%llx missing", key); - return; + return (-1); } dlen = strlen(ike->data); log_debug("found key 0x%llx: \"%s\"", key, ike->data); @@ -246,6 +246,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) if (key & KEYC_ESCAPE) bufferevent_write(wp->event, "\033", 1); bufferevent_write(wp->event, ike->data, dlen); + return (0); } /* Translate mouse and output. */ diff --git a/tmux.h b/tmux.h index 7c7dcc99..75e483c5 100644 --- a/tmux.h +++ b/tmux.h @@ -2275,7 +2275,7 @@ void input_parse(struct window_pane *); void input_parse_buffer(struct window_pane *, u_char *, size_t); /* input-key.c */ -void input_key(struct window_pane *, key_code, struct mouse_event *); +int input_key(struct window_pane *, key_code, struct mouse_event *); /* xterm-keys.c */ char *xterm_keys_lookup(key_code); @@ -2498,7 +2498,7 @@ int window_pane_set_mode(struct window_pane *, struct args *); void window_pane_reset_mode(struct window_pane *); void window_pane_reset_mode_all(struct window_pane *); -void window_pane_key(struct window_pane *, struct client *, +int window_pane_key(struct window_pane *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); int window_pane_visible(struct window_pane *); diff --git a/window.c b/window.c index 64c95855..bd25b5d1 100644 --- a/window.c +++ b/window.c @@ -1229,7 +1229,7 @@ window_pane_reset_mode_all(struct window_pane *wp) window_pane_reset_mode(wp); } -void +int window_pane_key(struct window_pane *wp, struct client *c, struct session *s, struct winlink *wl, key_code key, struct mouse_event *m) { @@ -1237,23 +1237,24 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, struct window_pane *wp2; if (KEYC_IS_MOUSE(key) && m == NULL) - return; + return (-1); wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { wp->modelast = time(NULL); if (wme->mode->key != NULL) wme->mode->key(wme, c, s, wl, (key & ~KEYC_XTERM), m); - return; + return (0); } if (wp->fd == -1 || wp->flags & PANE_INPUTOFF) - return; + return (0); - input_key(wp, key, m); + if (input_key(wp, key, m) != 0) + return (-1); if (KEYC_IS_MOUSE(key)) - return; + return (0); if (options_get_number(wp->window->options, "synchronize-panes")) { TAILQ_FOREACH(wp2, &wp->window->panes, entry) { if (wp2 != wp && @@ -1264,6 +1265,7 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, input_key(wp2, key, NULL); } } + return (0); } int From 835a6c0cf088437e318783c96b142c7dc26c290f Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jan 2020 08:12:53 +0000 Subject: [PATCH 20/40] Be more specific in the DSR we are looking for so it doesn't get confused with mouse sequences. Also set a flag and don't bother checking for it if we have already seen it (same for DA), and don't check if we never asked for it. --- tmux.h | 2 ++ tty-keys.c | 14 ++++++++++++++ tty.c | 5 +++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tmux.h b/tmux.h index 75e483c5..adc1a240 100644 --- a/tmux.h +++ b/tmux.h @@ -1227,6 +1227,8 @@ struct tty { #define TTY_OPENED 0x20 #define TTY_FOCUS 0x40 #define TTY_BLOCK 0x80 +#define TTY_HAVEDA 0x100 +#define TTY_HAVEDSR 0x200 int flags; struct tty_term *term; diff --git a/tty-keys.c b/tty-keys.c index 5054c424..968f67ca 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1020,6 +1020,8 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, int flags = 0; *size = 0; + if (tty->flags & TTY_HAVEDA) + return (-1); /* First three bytes are always \033[?. */ if (buf[0] != '\033') @@ -1064,7 +1066,10 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, for (i = 1; i < n; i++) log_debug("%s: DA feature: %d", c->name, p[i]); log_debug("%s: received DA %.*s", c->name, (int)*size, buf); + tty_set_flags(tty, flags); + tty->flags |= TTY_HAVEDA; + return (0); } @@ -1082,6 +1087,8 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, int flags = 0; *size = 0; + if (tty->flags & TTY_HAVEDSR) + return (-1); /* First three bytes are always \033[. */ if (buf[0] != '\033') @@ -1092,6 +1099,10 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, return (-1); if (len == 2) return (1); + if (buf[2] != 'I') + return (-1); + if (len == 3) + return (1); /* Copy the rest up to a 'n'. */ for (i = 0; i < (sizeof tmp) - 1 && buf[2 + i] != 'n'; i++) { @@ -1108,6 +1119,9 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, if (strncmp(tmp, "ITERM2 ", 7) == 0) flags |= TERM_DECSLRM; log_debug("%s: received DSR %.*s", c->name, (int)*size, buf); + tty_set_flags(tty, flags); + tty->flags |= TTY_HAVEDSR; + return (0); } diff --git a/tty.c b/tty.c index 2d4b4cc0..a1324528 100644 --- a/tty.c +++ b/tty.c @@ -327,8 +327,9 @@ tty_start_tty(struct tty *tty) tty->flags |= TTY_FOCUS; tty_puts(tty, "\033[?1004h"); } - tty_puts(tty, "\033[c\033[1337n"); - } + tty_puts(tty, "\033[c\033[1337n"); /* DA and DSR */ + } else + tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); tty->flags |= TTY_STARTED; tty_invalidate(tty); From da515570dc13daefc92e26a5c5417e1f190f80e8 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jan 2020 11:59:21 +0000 Subject: [PATCH 21/40] Stop handling DA and DSR after a second (they should be the first thing sent) so this should be plenty. --- tmux.h | 1 + tty.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/tmux.h b/tmux.h index adc1a240..ac555be5 100644 --- a/tmux.h +++ b/tmux.h @@ -1179,6 +1179,7 @@ LIST_HEAD(tty_terms, tty_term); struct tty { struct client *client; + struct event start_timer; u_int sx; u_int sy; diff --git a/tty.c b/tty.c index a1324528..3d58f05b 100644 --- a/tty.c +++ b/tty.c @@ -285,11 +285,22 @@ tty_open(struct tty *tty, char **cause) return (0); } +static void +tty_start_timer_callback(__unused int fd, __unused short events, void *data) +{ + struct tty *tty = data; + struct client *c = tty->client; + + log_debug("%s: start timer fired", c->name); + tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); +} + void tty_start_tty(struct tty *tty) { struct client *c = tty->client; struct termios tio; + struct timeval tv = { .tv_sec = 1 }; if (tty->fd != -1 && tcgetattr(tty->fd, &tty->tio) == 0) { setblocking(tty->fd, 0); @@ -328,9 +339,13 @@ tty_start_tty(struct tty *tty) tty_puts(tty, "\033[?1004h"); } tty_puts(tty, "\033[c\033[1337n"); /* DA and DSR */ + } else tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); + evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); + evtimer_add(&tty->start_timer, &tv); + tty->flags |= TTY_STARTED; tty_invalidate(tty); @@ -351,6 +366,8 @@ tty_stop_tty(struct tty *tty) return; tty->flags &= ~TTY_STARTED; + evtimer_del(&tty->start_timer); + event_del(&tty->timer); tty->flags &= ~TTY_BLOCK; From cdf138372c4b7a0cc48e15c1ad6412a88db08fb8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 14 Jan 2020 16:02:22 +0000 Subject: [PATCH 22/40] Add to CHANGES. --- CHANGES | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGES b/CHANGES index cd3a1b8a..ae980bb0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,22 @@ CHANGES FROM 3.0 to X.X +* Treat plausible but invalid keys (like C-BSpace) as literal like any other + unrecognised string passed to send-keys + +* Detect iTerm2 and enable use of DECSLRM (much faster with horizontally split + windows). + +* Add -Z to default switch-client command in tree mode. + +* Add ~ to quoted characters for %%%. + +* Document client exit messages in the manual page. + +* Do not let read-only clients limit the size. + +* Add a number of new formats to inspect what sessions and clients a window is + present or active in. + * Change file reading and writing to go through the client if necessary. This fixes commands like "tmux loadb /dev/fd/X". Also modify source-file to support "-" for standard input, like load-buffer and save-buffer. From 9169ee0e874a9e4c46eda77abca075b9ec38dbb6 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 25 Jan 2020 16:40:32 +0000 Subject: [PATCH 23/40] Mention swap-window -d, GitHub issue 2068. --- tmux.1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tmux.1 b/tmux.1 index 9c9cfb39..eb5087be 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2533,10 +2533,11 @@ This is similar to except the source and destination windows are swapped. It is an error if no window exists at .Ar src-window . +If +.Fl d +is given, the new window does not become the current window. .Pp -Like -.Ic swap-pane , -if +If .Fl s is omitted and a marked pane is present (see .Ic select-pane From 74b424075c6d0ee668343936624b12cea10f3f19 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sat, 25 Jan 2020 16:41:49 +0000 Subject: [PATCH 24/40] Use FNM_IGNORECASE if present, from Eric N Vander Weele in GitHub issue 2067. --- compat.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compat.h b/compat.h index 794ac67d..70801d0d 100644 --- a/compat.h +++ b/compat.h @@ -171,8 +171,12 @@ void warnx(const char *, ...); #endif #ifndef FNM_CASEFOLD +#ifdef FNM_IGNORECASE +#define FNM_CASEFOLD FNM_IGNORECASE +#else #define FNM_CASEFOLD 0 #endif +#endif #ifndef INFTIM #define INFTIM -1 From 2e39b621c9b29b58b4bfe761989b14f0f13d07b6 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 27 Jan 2020 08:23:42 +0000 Subject: [PATCH 25/40] Change so that assignments may be specified alone - a command isn't required. GitHub issue 2062. --- cmd-parse.y | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/cmd-parse.y b/cmd-parse.y index 97b50f57..41bcdaa8 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -133,7 +133,12 @@ statements : statement '\n' free($2); } -statement : condition +statement : /* empty */ + { + $$ = xmalloc (sizeof *$$); + TAILQ_INIT($$); + } + | condition { struct cmd_parse_state *ps = &parse_state; @@ -144,11 +149,6 @@ statement : condition cmd_parse_free_commands($1); } } - | assignment - { - $$ = xmalloc (sizeof *$$); - TAILQ_INIT($$); - } | commands { struct cmd_parse_state *ps = &parse_state; @@ -194,8 +194,10 @@ expanded : format free($1); } -assignment : /* empty */ - | EQUALS +optional_assignment : /* empty */ + | assignment + +assignment : EQUALS { struct cmd_parse_state *ps = &parse_state; int flags = ps->input->flags; @@ -372,7 +374,15 @@ commands : command $$ = $1; } -command : assignment TOKEN +command : assignment + { + struct cmd_parse_state *ps = &parse_state; + + $$ = xcalloc(1, sizeof *$$); + $$->name = NULL; + $$->line = ps->input->line; + } + | optional_assignment TOKEN { struct cmd_parse_state *ps = &parse_state; @@ -381,7 +391,7 @@ command : assignment TOKEN $$->line = ps->input->line; } - | assignment TOKEN arguments + | optional_assignment TOKEN arguments { struct cmd_parse_state *ps = &parse_state; @@ -631,6 +641,8 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds, * command list. */ TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) { + if (cmd->name == NULL) + continue; alias = cmd_get_alias(cmd->name); if (alias == NULL) continue; @@ -676,6 +688,8 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds, */ result = cmd_list_new(); TAILQ_FOREACH(cmd, cmds, entry) { + if (cmd->name == NULL) + continue; log_debug("%s: %u %s", __func__, cmd->line, cmd->name); cmd_log_argv(cmd->argc, cmd->argv, __func__); From d0b8d036be97efc885f879aa63ce9bbb0efd8222 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 27 Jan 2020 08:53:13 +0000 Subject: [PATCH 26/40] Add support for adding a note to a key binding (with bind-key -N) and use this to add descriptions to the default key bindings. A new -N flag to list-keys shows key bindings with notes rather than the default bind-key command used to create them. Change the default ? binding to use this to show a readable summary of keys. Also extend command-prompt to return the name of the key pressed and add a default binding (/) to show the note for the next key pressed Suggested by Alex Tremblay in GitHub issue 2000. --- cmd-bind-key.c | 12 ++-- cmd-command-prompt.c | 6 +- cmd-list-keys.c | 150 ++++++++++++++++++++++++++++++++++++-- key-bindings.c | 168 ++++++++++++++++++++++--------------------- status.c | 8 ++- tmux.1 | 48 ++++++++++--- tmux.h | 5 +- 7 files changed, 292 insertions(+), 105 deletions(-) diff --git a/cmd-bind-key.c b/cmd-bind-key.c index 2af15d29..27f75dd0 100644 --- a/cmd-bind-key.c +++ b/cmd-bind-key.c @@ -33,8 +33,8 @@ const struct cmd_entry cmd_bind_key_entry = { .name = "bind-key", .alias = "bind", - .args = { "cnrT:", 2, -1 }, - .usage = "[-cnr] [-T key-table] key " + .args = { "cnrN:T:", 2, -1 }, + .usage = "[-cnr] [-T key-table] [-N note] key " "command [arguments]", .flags = CMD_AFTERHOOK, @@ -46,10 +46,10 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; key_code key; - const char *tablename; + const char *tablename, *note; struct cmd_parse_result *pr; char **argv = args->argv; - int argc = args->argc; + int argc = args->argc, repeat; key = key_string_lookup_string(argv[0]); if (key == KEYC_NONE || key == KEYC_UNKNOWN) { @@ -63,6 +63,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) tablename = "root"; else tablename = "prefix"; + repeat = args_has(args, 'r'); if (argc == 2) pr = cmd_parse_from_string(argv[1], NULL); @@ -79,6 +80,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) case CMD_PARSE_SUCCESS: break; } - key_bindings_add(tablename, key, args_has(args, 'r'), pr->cmdlist); + note = args_get(args, 'N'); + key_bindings_add(tablename, key, note, repeat, pr->cmdlist); return (CMD_RETURN_NORMAL); } diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 603ddb0a..9f0ea19f 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -40,8 +40,8 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1iI:Np:t:", 0, 1 }, - .usage = "[-1Ni] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " + .args = { "1kiI:Np:t:", 0, 1 }, + .usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " "[template]", .flags = 0, @@ -122,6 +122,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_NUMERIC; else if (args_has(args, 'i')) cdata->flags |= PROMPT_INCREMENTAL; + else if (args_has(args, 'k')) + cdata->flags |= PROMPT_KEY; status_prompt_set(c, prompt, input, cmd_command_prompt_callback, cmd_command_prompt_free, cdata, cdata->flags); free(prompt); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 8636b70a..e9e75a72 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -36,8 +36,8 @@ const struct cmd_entry cmd_list_keys_entry = { .name = "list-keys", .alias = "lsk", - .args = { "T:", 0, 0 }, - .usage = "[-T key-table]", + .args = { "1NP:T:", 0, 1 }, + .usage = "[-1N] [-P prefix-string] [-T key-table] [key]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, .exec = cmd_list_keys_exec @@ -54,6 +54,88 @@ const struct cmd_entry cmd_list_commands_entry = { .exec = cmd_list_keys_exec }; +static u_int +cmd_list_keys_get_width(const char *tablename, key_code only) +{ + struct key_table *table; + struct key_binding *bd; + u_int width, keywidth = 0; + + table = key_bindings_get_table(tablename, 0); + if (table == NULL) + return (0); + bd = key_bindings_first(table); + while (bd != NULL) { + if ((only != KEYC_UNKNOWN && bd->key != only) || + KEYC_IS_MOUSE(bd->key) || + bd->note == NULL) { + bd = key_bindings_next(table, bd); + continue; + } + width = utf8_cstrwidth(key_string_lookup_key(bd->key)); + if (width > keywidth) + keywidth = width; + + bd = key_bindings_next(table, bd); + } + return (keywidth); +} + +static int +cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, + const char *tablename, u_int keywidth, key_code only, const char *prefix) +{ + struct client *c = cmd_find_client(item, NULL, 1); + struct key_table *table; + struct key_binding *bd; + const char *key; + char *tmp; + int found = 0; + + table = key_bindings_get_table(tablename, 0); + if (table == NULL) + return (0); + bd = key_bindings_first(table); + while (bd != NULL) { + if ((only != KEYC_UNKNOWN && bd->key != only) || + KEYC_IS_MOUSE(bd->key) || + bd->note == NULL) { + bd = key_bindings_next(table, bd); + continue; + } + found = 1; + key = key_string_lookup_key(bd->key); + + tmp = utf8_padcstr(key, keywidth + 1); + if (args_has(args, '1') && c != NULL) + status_message_set(c, "%s%s%s", prefix, tmp, bd->note); + else + cmdq_print(item, "%s%s%s", prefix, tmp, bd->note); + free(tmp); + + if (args_has(args, '1')) + break; + bd = key_bindings_next(table, bd); + } + return (found); +} + +static char * +cmd_list_keys_get_prefix(struct args *args, key_code *prefix) +{ + char *s; + + *prefix = options_get_number(global_s_options, "prefix"); + if (!args_has(args, 'P')) { + if (*prefix != KEYC_NONE) + xasprintf(&s, "%s ", key_string_lookup_key(*prefix)); + else + s = xstrdup(""); + } else + s = xstrdup(args_get(args, 'P')); + return (s); +} + static enum cmd_retval cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) { @@ -61,19 +143,63 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) struct key_table *table; struct key_binding *bd; const char *tablename, *r; - char *key, *cp, *tmp; - int repeat, width, tablewidth, keywidth; + char *key, *cp, *tmp, *start, *empty; + key_code prefix, only = KEYC_UNKNOWN; + int repeat, width, tablewidth, keywidth, found = 0; size_t tmpsize, tmpused, cplen; if (self->entry == &cmd_list_commands_entry) return (cmd_list_keys_commands(self, item)); + if (args->argc != 0) { + only = key_string_lookup_string(args->argv[0]); + if (only == KEYC_UNKNOWN) { + cmdq_error(item, "invalid key: %s", args->argv[0]); + return (CMD_RETURN_ERROR); + } + } + tablename = args_get(args, 'T'); if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) { cmdq_error(item, "table %s doesn't exist", tablename); return (CMD_RETURN_ERROR); } + if (args_has(args, 'N')) { + if (tablename == NULL) { + start = cmd_list_keys_get_prefix(args, &prefix); + keywidth = cmd_list_keys_get_width("root", only); + if (prefix != KEYC_NONE) { + width = cmd_list_keys_get_width("prefix", only); + if (width == 0) + prefix = KEYC_NONE; + else if (width > keywidth) + keywidth = width; + } + empty = utf8_padcstr("", utf8_cstrwidth(start)); + + found = cmd_list_keys_print_notes(item, args, "root", + keywidth, only, empty); + if (prefix != KEYC_NONE) { + if (cmd_list_keys_print_notes(item, args, + "prefix", keywidth, only, start)) + found = 1; + } + free(empty); + } else { + if (args_has(args, 'P')) + start = xstrdup(args_get(args, 'P')); + else + start = xstrdup(""); + keywidth = cmd_list_keys_get_width(tablename, only); + found = cmd_list_keys_print_notes(item, args, tablename, + keywidth, only, start); + + } + free(start); + goto out; + } + repeat = 0; tablewidth = keywidth = 0; table = key_bindings_first_table (); @@ -84,6 +210,10 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) } bd = key_bindings_first(table); while (bd != NULL) { + if (only != KEYC_UNKNOWN && bd->key != only) { + bd = key_bindings_next(table, bd); + continue; + } key = args_escape(key_string_lookup_key(bd->key)); if (bd->flags & KEY_BINDING_REPEAT) @@ -113,6 +243,11 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) } bd = key_bindings_first(table); while (bd != NULL) { + if (only != KEYC_UNKNOWN && bd->key != only) { + bd = key_bindings_next(table, bd); + continue; + } + found = 1; key = args_escape(key_string_lookup_key(bd->key)); if (!repeat) @@ -162,13 +297,18 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) free(tmp); +out: + if (only != KEYC_UNKNOWN && !found) { + cmdq_error(item, "unknown key: %s", args->argv[0]); + return (CMD_RETURN_ERROR); + } return (CMD_RETURN_NORMAL); } static enum cmd_retval cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = self->args; const struct cmd_entry **entryp; const struct cmd_entry *entry; struct format_tree *ft; diff --git a/key-bindings.c b/key-bindings.c index 94110a49..4387c011 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -90,6 +90,7 @@ key_bindings_free(struct key_table *table, struct key_binding *bd) { RB_REMOVE(key_bindings, &table->key_bindings, bd); cmd_list_free(bd->cmdlist); + free((void *)bd->note); free(bd); } @@ -163,7 +164,7 @@ key_bindings_next(__unused struct key_table *table, struct key_binding *bd) } void -key_bindings_add(const char *name, key_code key, int repeat, +key_bindings_add(const char *name, key_code key, const char *note, int repeat, struct cmd_list *cmdlist) { struct key_table *table; @@ -177,6 +178,8 @@ key_bindings_add(const char *name, key_code key, int repeat, bd = xcalloc(1, sizeof *bd); bd->key = key; + if (note != NULL) + bd->note = xstrdup(note); RB_INSERT(key_bindings, &table->key_bindings, bd); if (repeat) @@ -226,87 +229,88 @@ void key_bindings_init(void) { static const char *defaults[] = { - "bind C-b send-prefix", - "bind C-o rotate-window", - "bind C-z suspend-client", - "bind Space next-layout", - "bind ! break-pane", - "bind '\"' split-window", - "bind '#' list-buffers", - "bind '$' command-prompt -I'#S' \"rename-session -- '%%'\"", - "bind % split-window -h", - "bind & confirm-before -p\"kill-window #W? (y/n)\" kill-window", - "bind \"'\" command-prompt -pindex \"select-window -t ':%%'\"", - "bind ( switch-client -p", - "bind ) switch-client -n", - "bind , command-prompt -I'#W' \"rename-window -- '%%'\"", - "bind - delete-buffer", - "bind . command-prompt \"move-window -t '%%'\"", - "bind 0 select-window -t:=0", - "bind 1 select-window -t:=1", - "bind 2 select-window -t:=2", - "bind 3 select-window -t:=3", - "bind 4 select-window -t:=4", - "bind 5 select-window -t:=5", - "bind 6 select-window -t:=6", - "bind 7 select-window -t:=7", - "bind 8 select-window -t:=8", - "bind 9 select-window -t:=9", - "bind : command-prompt", - "bind \\; last-pane", - "bind = choose-buffer -Z", - "bind ? list-keys", - "bind D choose-client -Z", - "bind E select-layout -E", - "bind L switch-client -l", - "bind M select-pane -M", - "bind [ copy-mode", - "bind ] paste-buffer", - "bind c new-window", - "bind d detach-client", - "bind f command-prompt \"find-window -Z -- '%%'\"", - "bind i display-message", - "bind l last-window", - "bind m select-pane -m", - "bind n next-window", - "bind o select-pane -t:.+", - "bind p previous-window", - "bind q display-panes", - "bind r refresh-client", - "bind s choose-tree -Zs", - "bind t clock-mode", - "bind w choose-tree -Zw", - "bind x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", - "bind z resize-pane -Z", - "bind '{' swap-pane -U", - "bind '}' swap-pane -D", - "bind '~' show-messages", - "bind PPage copy-mode -u", - "bind -r Up select-pane -U", - "bind -r Down select-pane -D", - "bind -r Left select-pane -L", - "bind -r Right select-pane -R", - "bind M-1 select-layout even-horizontal", - "bind M-2 select-layout even-vertical", - "bind M-3 select-layout main-horizontal", - "bind M-4 select-layout main-vertical", - "bind M-5 select-layout tiled", - "bind M-n next-window -a", - "bind M-o rotate-window -D", - "bind M-p previous-window -a", - "bind -r S-Up refresh-client -U 10", - "bind -r S-Down refresh-client -D 10", - "bind -r S-Left refresh-client -L 10", - "bind -r S-Right refresh-client -R 10", - "bind -r DC refresh-client -c", - "bind -r M-Up resize-pane -U 5", - "bind -r M-Down resize-pane -D 5", - "bind -r M-Left resize-pane -L 5", - "bind -r M-Right resize-pane -R 5", - "bind -r C-Up resize-pane -U", - "bind -r C-Down resize-pane -D", - "bind -r C-Left resize-pane -L", - "bind -r C-Right resize-pane -R", + "bind -N 'Send the prefix key' C-b send-prefix", + "bind -N 'Rotate through the panes' C-o rotate-window", + "bind -N 'Suspend the current client' C-z suspend-client", + "bind -N 'Select next layout' Space next-layout", + "bind -N 'Break pane to a new window' ! break-pane", + "bind -N 'Split window vertically' '\"' split-window", + "bind -N 'List all paste buffers' '#' list-buffers", + "bind -N 'Rename current session' '$' command-prompt -I'#S' \"rename-session -- '%%'\"", + "bind -N 'Split window horizontally' % split-window -h", + "bind -N 'Kill current window' & confirm-before -p\"kill-window #W? (y/n)\" kill-window", + "bind -N 'Prompt for window index to select' \"'\" command-prompt -pindex \"select-window -t ':%%'\"", + "bind -N 'Switch to previous client' ( switch-client -p", + "bind -N 'Switch to next client' ) switch-client -n", + "bind -N 'Rename current window' , command-prompt -I'#W' \"rename-window -- '%%'\"", + "bind -N 'Delete the most recent paste buffer' - delete-buffer", + "bind -N 'Move the current window' . command-prompt \"move-window -t '%%'\"", + "bind -N 'Describe key binding' '/' command-prompt -kpkey 'list-keys -1N \"%%%\"'", + "bind -N 'Select window 0' 0 select-window -t:=0", + "bind -N 'Select window 1' 1 select-window -t:=1", + "bind -N 'Select window 2' 2 select-window -t:=2", + "bind -N 'Select window 3' 3 select-window -t:=3", + "bind -N 'Select window 4' 4 select-window -t:=4", + "bind -N 'Select window 5' 5 select-window -t:=5", + "bind -N 'Select window 6' 6 select-window -t:=6", + "bind -N 'Select window 7' 7 select-window -t:=7", + "bind -N 'Select window 8' 8 select-window -t:=8", + "bind -N 'Select window 9' 9 select-window -t:=9", + "bind -N 'Prompt for a command' : command-prompt", + "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 '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", + "bind -N 'Enter copy mode' [ copy-mode", + "bind -N 'Paste the most recent paste buffer' ] paste-buffer", + "bind -N 'Create a new window' c new-window", + "bind -N 'Detach the current client' d detach-client", + "bind -N 'Search for a pane' f command-prompt \"find-window -Z -- '%%'\"", + "bind -N 'Display window information' i display-message", + "bind -N 'Select the previously current window' l last-window", + "bind -N 'Toggle the marked pane' m select-pane -m", + "bind -N 'Select the next window' n next-window", + "bind -N 'Select the next pane' o select-pane -t:.+", + "bind -N 'Select the previous pane' p previous-window", + "bind -N 'Display pane numbers' q display-panes", + "bind -N 'Redraw the current client' r refresh-client", + "bind -N 'Choose a session from a list' s choose-tree -Zs", + "bind -N 'Show a clock' t clock-mode", + "bind -N 'Choose a window from a list' w choose-tree -Zw", + "bind -N 'Kill the active pane' x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", + "bind -N 'Zoom the active pane' z resize-pane -Z", + "bind -N 'Swap the active pane with the pane above' '{' swap-pane -U", + "bind -N 'Swap the active pane with the pane below' '}' swap-pane -D", + "bind -N 'Show messages' '~' show-messages", + "bind -N 'Enter copy mode and scroll up' PPage copy-mode -u", + "bind -N 'Select the pane above the active pane' -r Up select-pane -U", + "bind -N 'Select the pane below the active pane' -r Down select-pane -D", + "bind -N 'Select the pane to the left of the active pane' -r Left select-pane -L", + "bind -N 'Select the pane to the right of the active pane' -r Right select-pane -R", + "bind -N 'Set the even-horizontal layout' M-1 select-layout even-horizontal", + "bind -N 'Set the even-vertical layout' M-2 select-layout even-vertical", + "bind -N 'Set the main-horizontal layout' M-3 select-layout main-horizontal", + "bind -N 'Set the main-vertical layout' M-4 select-layout main-vertical", + "bind -N 'Select the tiled layout' M-5 select-layout tiled", + "bind -N 'Select the next window with an alert' M-n next-window -a", + "bind -N 'Rotate through the panes in reverse' M-o rotate-window -D", + "bind -N 'Select the previous window with an alert' M-p previous-window -a", + "bind -N 'Move the visible part of the window up' -r S-Up refresh-client -U 10", + "bind -N 'Move the visible part of the window down' -r S-Down refresh-client -D 10", + "bind -N 'Move the visible part of the window left' -r S-Left refresh-client -L 10", + "bind -N 'Move the visible part of the window right' -r S-Right refresh-client -R 10", + "bind -N 'Reset so the visible part of the window follows the cursor' -r DC refresh-client -c", + "bind -N 'Resize the pane up by 5' -r M-Up resize-pane -U 5", + "bind -N 'Resize the pane down by 5' -r M-Down resize-pane -D 5", + "bind -N 'Resize the pane left by 5' -r M-Left resize-pane -L 5", + "bind -N 'Resize the pane right by 5' -r M-Right resize-pane -R 5", + "bind -N 'Resize the pane up' -r C-Up resize-pane -U", + "bind -N 'Resize the pane down' -r C-Down resize-pane -D", + "bind -N 'Resize the pane left' -r C-Left resize-pane -L", + "bind -N 'Resize the pane right' -r C-Right resize-pane -R", "bind -n MouseDown1Pane select-pane -t=\\; send-keys -M", "bind -n MouseDrag1Border resize-pane -M", diff --git a/status.c b/status.c index 0f96f0d3..33f6c47a 100644 --- a/status.c +++ b/status.c @@ -915,11 +915,17 @@ status_prompt_key(struct client *c, key_code key) { struct options *oo = c->session->options; char *s, *cp, word[64], prefix = '='; - const char *histstr, *ws = NULL; + const char *histstr, *ws = NULL, *keystring; size_t size, n, off, idx, used; struct utf8_data tmp, *first, *last, *ud; int keys; + if (c->prompt_flags & PROMPT_KEY) { + keystring = key_string_lookup_key(key); + c->prompt_inputcb(c, c->prompt_data, keystring, 1); + status_prompt_clear(c); + return (0); + } size = utf8_strlen(c->prompt_buffer); if (c->prompt_flags & PROMPT_NUMERIC) { diff --git a/tmux.1 b/tmux.1 index eb5087be..4a618186 100644 --- a/tmux.1 +++ b/tmux.1 @@ -551,7 +551,7 @@ Braces may be enclosed inside braces, for example: .Bd -literal -offset indent bind x if-shell "true" { if-shell "true" { - display "true!" + display "true!" } } .Ed @@ -1335,7 +1335,8 @@ is used, option will not be applied. .Pp .Fl T -sets the client's key table; the next key from the client will be interpreted from +sets the client's key table; the next key from the client will be interpreted +from .Ar key-table . This may be used to configure multiple prefix keys, or to bind commands to sequences of keys. @@ -2613,6 +2614,7 @@ Commands related to key bindings are as follows: .Bl -tag -width Ds .It Xo Ic bind-key .Op Fl nr +.Op Fl N Ar note .Op Fl T Ar key-table .Ar key Ar command Op Ar arguments .Xc @@ -2660,22 +2662,46 @@ The flag indicates this key may repeat, see the .Ic repeat-time option. +.Fl N +attaches a note to the key (shown with +.Ic list-keys +.Fl N ) . .Pp To view the default bindings and possible commands, see the .Ic list-keys command. .It Xo Ic list-keys -.Op Fl T Ar key-table +.Op Fl 1N +.Op Fl P Ar prefix-string Fl T Ar key-table +.Op key .Xc .D1 (alias: Ic lsk ) List all key bindings. -Without -.Fl T -all key tables are printed. +By default this shows all keys or any bindings for +.Ar key +in the syntax of the +.Ic bind-key +command. +.Fl N +instead show keys and attached notes, i +.Ar key-table +if given or in the +.Em root +and +.Em prefix +key tables by default. +.Fl P +specifies a prefix to print before each key. With +.Fl 1 +only the first matching key and note is shown. +.Pp +Without +.Fl N , .Fl T -only -.Ar key-table . +prints only keys in +.Ar key-table , +otherwise all key tables are printed. .It Xo Ic send-keys .Op Fl FHlMRX .Op Fl N Ar repeat-count @@ -4693,7 +4719,7 @@ session option. Commands related to the status line are as follows: .Bl -tag -width Ds .It Xo Ic command-prompt -.Op Fl 1Ni +.Op Fl 1ikN .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client @@ -4743,6 +4769,10 @@ but any quotation marks are escaped. .Fl 1 makes the prompt only accept one key press, in this case the resulting input is a single character. +.Fl k +is like +.Fl 1 +but the key press is translated to a key name. .Fl N makes the prompt only accept numeric key presses. .Fl i diff --git a/tmux.h b/tmux.h index ac555be5..093f17a4 100644 --- a/tmux.h +++ b/tmux.h @@ -1609,6 +1609,7 @@ struct client { #define PROMPT_NUMERIC 0x2 #define PROMPT_INCREMENTAL 0x4 #define PROMPT_NOFORMAT 0x8 +#define PROMPT_KEY 0x10 int prompt_flags; struct session *session; @@ -1636,6 +1637,7 @@ TAILQ_HEAD(clients, client); struct key_binding { key_code key; struct cmd_list *cmdlist; + const char *note; int flags; #define KEY_BINDING_REPEAT 0x1 @@ -2147,7 +2149,8 @@ void key_bindings_unref_table(struct key_table *); struct key_binding *key_bindings_get(struct key_table *, key_code); struct key_binding *key_bindings_first(struct key_table *); struct key_binding *key_bindings_next(struct key_table *, struct key_binding *); -void key_bindings_add(const char *, key_code, int, struct cmd_list *); +void key_bindings_add(const char *, key_code, const char *, int, + struct cmd_list *); void key_bindings_remove(const char *, key_code); void key_bindings_remove_table(const char *); void key_bindings_init(void); From 2c38e01b548553aa162f9f126147b5ed64fd1700 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 27 Jan 2020 09:04:47 +0000 Subject: [PATCH 27/40] Expand description of start-server. --- tmux.1 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tmux.1 b/tmux.1 index 4a618186..11bdf4bc 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1287,6 +1287,17 @@ shows the parsed commands and line numbers if possible. Start the .Nm 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 +.Pa ~/.tmux.conf , +.Ic exit-empty +is turned off, or another command is run as part of the same command sequence. +For example: +.Bd -literal -offset indent +$ tmux start \\; show -g +.Ed .It Xo Ic suspend-client .Op Fl t Ar target-client .Xc From 24350879cdfb9ef23dee0da409b621e9830d8baf Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 08:06:11 +0000 Subject: [PATCH 28/40] Add a define for flags meaning a client is not attached, and fix unattached counter, reported by Thomas Sattler. --- resize.c | 5 +++-- server-client.c | 4 ++-- tmux.h | 4 ++++ window-client.c | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/resize.c b/resize.c index 054b025f..b4142a70 100644 --- a/resize.c +++ b/resize.c @@ -363,14 +363,15 @@ recalculate_sizes(void) * client. */ TAILQ_FOREACH(c, &clients, entry) { + s = c->session; + if (s != NULL && !(c->flags & CLIENT_UNATTACHEDFLAGS)) + s->attached++; if (ignore_client_size(c)) continue; - s = c->session; if (c->tty.sy <= s->statuslines || (c->flags & CLIENT_CONTROL)) c->flags |= CLIENT_STATUSOFF; else c->flags &= ~CLIENT_STATUSOFF; - s->attached++; } /* Walk each window and adjust the size. */ diff --git a/server-client.c b/server-client.c index ee7b4c70..12e07327 100644 --- a/server-client.c +++ b/server-client.c @@ -1034,7 +1034,7 @@ server_client_key_callback(struct cmdq_item *item, void *data) key_code key0; /* Check the client is good to accept input. */ - if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) goto out; wl = s->curw; @@ -1221,7 +1221,7 @@ server_client_handle_key(struct client *c, struct key_event *event) struct cmdq_item *item; /* Check the client is good to accept input. */ - if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return (0); /* diff --git a/tmux.h b/tmux.h index 093f17a4..d56c5fea 100644 --- a/tmux.h +++ b/tmux.h @@ -1582,6 +1582,10 @@ struct client { CLIENT_REDRAWSTATUSALWAYS| \ CLIENT_REDRAWBORDERS| \ CLIENT_REDRAWOVERLAY) +#define CLIENT_UNATTACHEDFLAGS \ + (CLIENT_DEAD| \ + CLIENT_SUSPENDED| \ + CLIENT_DETACHING) #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ diff --git a/window-client.c b/window-client.c index 22a0f2e2..4688cbf3 100644 --- a/window-client.c +++ b/window-client.c @@ -210,7 +210,7 @@ window_client_draw(__unused void *modedata, void *itemdata, struct window_pane *wp; u_int cx = s->cx, cy = s->cy, lines, at; - if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) + if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; wp = c->session->curw->window->active; From 32816eaebd4ccd712cc7f7291a587146d122a9ff Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 10:21:21 +0000 Subject: [PATCH 29/40] Set up working directory before killing the existing pane on respawn. --- spawn.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/spawn.c b/spawn.c index 75995221..933f0c6a 100644 --- a/spawn.c +++ b/spawn.c @@ -225,6 +225,17 @@ spawn_pane(struct spawn_context *sc, char **cause) spawn_log(__func__, sc); + /* + * Work out the current working directory. If respawning, use + * the pane's stored one unless specified. + */ + if (sc->cwd != NULL) + cwd = format_single(item, sc->cwd, c, s, NULL, NULL); + else if (~sc->flags & SPAWN_RESPAWN) + cwd = xstrdup(server_client_get_cwd(c, s)); + else + cwd = NULL; + /* * If we are respawning then get rid of the old process. Otherwise * either create a new cell or assign to the one we are given. @@ -235,6 +246,7 @@ spawn_pane(struct spawn_context *sc, char **cause) window_pane_index(sc->wp0, &idx); xasprintf(cause, "pane %s:%d.%u still active", s->name, sc->wl->idx, idx); + free(cwd); return (NULL); } if (sc->wp0->fd != -1) { @@ -255,8 +267,8 @@ spawn_pane(struct spawn_context *sc, char **cause) } /* - * Now we have a pane with nothing running in it ready for the new - * process. Work out the command and arguments. + * Now we have a pane with nothing running in it ready for the new process. + * Work out the command and arguments and store the working directory. */ if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) { cmd = options_get_string(s->options, "default-command"); @@ -271,6 +283,10 @@ spawn_pane(struct spawn_context *sc, char **cause) argc = sc->argc; argv = sc->argv; } + if (cwd != NULL) { + free(new_wp->cwd); + new_wp->cwd = cwd; + } /* * Replace the stored arguments if there are new ones. If not, the @@ -282,21 +298,6 @@ spawn_pane(struct spawn_context *sc, char **cause) new_wp->argv = cmd_copy_argv(argc, argv); } - /* - * Work out the current working directory. If respawning, use - * the pane's stored one unless specified. - */ - if (sc->cwd != NULL) - cwd = format_single(item, sc->cwd, c, s, NULL, NULL); - else if (~sc->flags & SPAWN_RESPAWN) - cwd = xstrdup(server_client_get_cwd(c, s)); - else - cwd = NULL; - if (cwd != NULL) { - free(new_wp->cwd); - new_wp->cwd = cwd; - } - /* Create an environment for this pane. */ child = environ_for_session(s, 0); if (sc->environ != NULL) From f165221dc4641837ee9f589bb666d310a495904c Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 10:44:30 +0000 Subject: [PATCH 30/40] Reduce a difference with portable tmux by adding the -V flag and #{version} format; on OpenBSD these just report the OpenBSD version. --- format.c | 1 + proc.c | 4 ++-- tmux.1 | 8 +++++++- tmux.c | 20 +++++++++++++++++++- tmux.h | 1 + 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/format.c b/format.c index f5e7b2ce..ff8e3c62 100644 --- a/format.c +++ b/format.c @@ -1106,6 +1106,7 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags) ft->flags = flags; ft->time = time(NULL); + format_add(ft, "version", "%s", getversion()); format_add_cb(ft, "host", format_cb_host); format_add_cb(ft, "host_short", format_cb_host_short); format_add_cb(ft, "pid", format_cb_pid); diff --git a/proc.c b/proc.c index 48e0d90c..3fc190a2 100644 --- a/proc.c +++ b/proc.c @@ -182,8 +182,8 @@ proc_start(const char *name) if (uname(&u) < 0) memset(&u, 0, sizeof u); - log_debug("%s started (%ld): socket %s, protocol %d", name, - (long)getpid(), socket_path, PROTOCOL_VERSION); + log_debug("%s started (%ld): version %s, socket %s, protocol %d", name, + (long)getpid(), getversion(), socket_path, PROTOCOL_VERSION); log_debug("on %s %s %s; libevent %s (%s)", u.sysname, u.release, u.version, event_get_version(), event_get_method()); diff --git a/tmux.1 b/tmux.1 index 11bdf4bc..bc24c193 100644 --- a/tmux.1 +++ b/tmux.1 @@ -23,7 +23,7 @@ .Sh SYNOPSIS .Nm tmux .Bk -words -.Op Fl 2Cluv +.Op Fl 2CluvV .Op Fl c Ar shell-command .Op Fl f Ar file .Op Fl L Ar socket-name @@ -210,6 +210,11 @@ signal may be sent to the server process to toggle logging between on (as if .Fl v was given) and off. +.It Fl V +Report the +.Nm +version. +.Pp .It Ar command Op Ar flags This specifies one of a set of commands used to control .Nm , @@ -4369,6 +4374,7 @@ The following variables are available, where appropriate: .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "start_time" Ta "" Ta "Server start time" +.It Li "version" Ta "" Ta "Server version" .It Li "window_active" Ta "" Ta "1 if window active" .It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window" .It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window" diff --git a/tmux.c b/tmux.c index fe2647f5..f98037b5 100644 --- a/tmux.c +++ b/tmux.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -212,6 +213,20 @@ find_home(void) return (home); } +const char * +getversion(void) +{ + static char *version; + struct utsname u; + + if (version == NULL) { + if (uname(&u) < 0) + fatalx("uname failed"); + xasprintf(&version, "openbsd-%s", u.release); + } + return version; +} + int main(int argc, char **argv) { @@ -238,7 +253,7 @@ main(int argc, char **argv) flags = 0; label = path = NULL; - while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) { + while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUvV")) != -1) { switch (opt) { case '2': flags |= CLIENT_256COLOURS; @@ -255,6 +270,9 @@ main(int argc, char **argv) case 'f': set_cfg_file(optarg); break; + case 'V': + printf("%s %s\n", getprogname(), getversion()); + exit(0); case 'l': flags |= CLIENT_LOGIN; break; diff --git a/tmux.h b/tmux.h index d56c5fea..24e4277f 100644 --- a/tmux.h +++ b/tmux.h @@ -1769,6 +1769,7 @@ int areshell(const char *); void setblocking(int, int); const char *find_cwd(void); const char *find_home(void); +const char *getversion(void); /* proc.c */ struct imsg; From 90e962fff8f4d251cdf7fcc653caa34973c82d91 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 10:59:29 +0000 Subject: [PATCH 31/40] Add support for the iTerm2 DSR 1337 sequence to get the terminal version. --- input.c | 9 +++++++++ tty-keys.c | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/input.c b/input.c index 452eac7f..d7071bfe 100644 --- a/input.c +++ b/input.c @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -1301,6 +1302,7 @@ input_csi_dispatch(struct input_ctx *ictx) struct input_table_entry *entry; int i, n, m; u_int cx, bg = ictx->cell.cell.bg; + char *copy, *cp; if (ictx->flags & INPUT_DISCARD) return (0); @@ -1432,6 +1434,13 @@ input_csi_dispatch(struct input_ctx *ictx) case 6: input_reply(ictx, "\033[%u;%uR", s->cy + 1, s->cx + 1); break; + case 1337: /* Terminal version, from iTerm2. */ + copy = xstrdup(getversion()); + for (cp = copy; *cp != '\0'; cp++) + *cp = toupper((u_char)*cp); + input_reply(ictx, "\033[TMUX %sn", copy); + free(copy); + break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; diff --git a/tty-keys.c b/tty-keys.c index 968f67ca..987fd1b0 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1099,7 +1099,7 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, return (-1); if (len == 2) return (1); - if (buf[2] != 'I') + if (buf[2] != 'I' && buf[2] != 'T') return (-1); if (len == 3) return (1); From 685eb381dec7fc741a15ce11a84d8c96ed66ce42 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 28 Jan 2020 11:28:30 +0000 Subject: [PATCH 32/40] Fix for version changes. --- Makefile.am | 5 +++-- input.c | 1 + tmux.c | 13 +------------ 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/Makefile.am b/Makefile.am index 6902477e..082a467c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,8 +11,9 @@ EXTRA_DIST = \ dist_EXTRA_tmux_SOURCES = compat/*.[ch] # Preprocessor flags. -AM_CPPFLAGS += @XOPEN_DEFINES@ -AM_CPPFLAGS += -DTMUX_CONF="\"$(sysconfdir)/tmux.conf:~/.tmux.conf:~/.config/tmux/tmux.conf\"" +AM_CPPFLAGS += @XOPEN_DEFINES@ \ + -DTMUX_VERSION="\"@VERSION@\"" \ + -DTMUX_CONF="\"$(sysconfdir)/tmux.conf:~/.tmux.conf:~/.config/tmux/tmux.conf\"" # Additional object files. LDADD = $(LIBOBJS) diff --git a/input.c b/input.c index 452eac7f..a2c63e14 100644 --- a/input.c +++ b/input.c @@ -20,6 +20,7 @@ #include +#include #include #include #include diff --git a/tmux.c b/tmux.c index b5cdfbf9..3c1feccc 100644 --- a/tmux.c +++ b/tmux.c @@ -213,15 +213,7 @@ find_home(void) const char * getversion(void) { - static char *version; - struct utsname u; - - if (version == NULL) { - if (uname(&u) < 0) - fatalx("uname failed"); - xasprintf(&version, "openbsd-%s", u.release); - } - return version; + return TMUX_VERSION; } int @@ -264,9 +256,6 @@ main(int argc, char **argv) else flags |= CLIENT_CONTROL; break; - case 'V': - printf("%s %s\n", getprogname(), VERSION); - exit(0); case 'f': set_cfg_file(optarg); break; From 84995ae1726589b5fb04e002e43496775e0ebfcd Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 11:31:31 +0000 Subject: [PATCH 33/40] -V also needs to go in usage. --- tmux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmux.c b/tmux.c index f98037b5..6572ad4e 100644 --- a/tmux.c +++ b/tmux.c @@ -56,7 +56,7 @@ static __dead void usage(void) { fprintf(stderr, - "usage: %s [-2Cluv] [-c shell-command] [-f file] [-L socket-name]\n" + "usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n" " [-S socket-path] [command [flags]]\n", getprogname()); exit(1); From a6129e99749d2bbc8b4a991c7b5d09300aa55f39 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 11:39:51 +0000 Subject: [PATCH 34/40] If we can identify the terminal as iTerm2 or as tmux, we can be sure they support 256 and RGB colours, so set those flags too. --- tmux.h | 1 + tty-keys.c | 4 +++- tty-term.c | 28 +++++++++------------------- tty.c | 27 +++++++++++++++------------ 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/tmux.h b/tmux.h index 24e4277f..3ec89019 100644 --- a/tmux.h +++ b/tmux.h @@ -1171,6 +1171,7 @@ struct tty_term { #define TERM_NOXENL 0x2 #define TERM_DECSLRM 0x4 #define TERM_DECFRA 0x8 +#define TERM_RGBCOLOURS 0x10 int flags; LIST_ENTRY(tty_term) entry; diff --git a/tty-keys.c b/tty-keys.c index 987fd1b0..064f2172 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1117,7 +1117,9 @@ tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, /* Set terminal flags. */ if (strncmp(tmp, "ITERM2 ", 7) == 0) - flags |= TERM_DECSLRM; + flags |= (TERM_DECSLRM|TERM_256COLOURS|TERM_RGBCOLOURS); + if (strncmp(tmp, "TMUX ", 5) == 0) + flags |= (TERM_256COLOURS|TERM_RGBCOLOURS); log_debug("%s: received DSR %.*s", c->name, (int)*size, buf); tty_set_flags(tty, flags); diff --git a/tty-term.c b/tty-term.c index 164ab2f1..ef7d7905 100644 --- a/tty-term.c +++ b/tty-term.c @@ -522,11 +522,16 @@ tty_term_find(char *name, int fd, char **cause) goto error; } - /* Figure out if we have 256 colours (or more). */ - if (tty_term_number(term, TTYC_COLORS) >= 256 || - tty_term_has(term, TTYC_RGB)) + /* Set flag if terminal has 256 colours. */ + if (tty_term_number(term, TTYC_COLORS) >= 256) term->flags |= TERM_256COLOURS; + /* Set flag if terminal has RGB colours. */ + if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) || + (tty_term_has(term, TTYC_SETRGBF) && + tty_term_has(term, TTYC_SETRGBB))) + term->flags |= TERM_RGBCOLOURS; + /* * Terminals without xenl (eat newline glitch) wrap at at $COLUMNS - 1 * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). @@ -561,22 +566,7 @@ tty_term_find(char *name, int fd, char **cause) code->type = TTYCODE_STRING; } - /* - * On terminals with RGB colour (Tc or RGB), fill in setrgbf and - * setrgbb if they are missing. - */ - if ((tty_term_flag(term, TTYC_TC) || tty_term_flag(term, TTYC_RGB)) && - !tty_term_has(term, TTYC_SETRGBF) && - !tty_term_has(term, TTYC_SETRGBB)) { - code = &term->codes[TTYC_SETRGBF]; - code->value.string = xstrdup("\033[38;2;%p1%d;%p2%d;%p3%dm"); - code->type = TTYCODE_STRING; - code = &term->codes[TTYC_SETRGBB]; - code->value.string = xstrdup("\033[48;2;%p1%d;%p2%d;%p3%dm"); - code->type = TTYCODE_STRING; - } - - /* Log it. */ + /* Log the capabilities. */ for (i = 0; i < tty_term_ncodes(); i++) log_debug("%s%s", name, tty_term_describe(term, i)); diff --git a/tty.c b/tty.c index 3d58f05b..54c3be30 100644 --- a/tty.c +++ b/tty.c @@ -2386,11 +2386,10 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Is this a 24-bit colour? */ if (gc->fg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if (!tty_term_has(tty->term, TTYC_SETRGBF)) { - colour_split_rgb(gc->fg, &r, &g, &b); - gc->fg = colour_find_rgb(r, g, b); - } else + if ((tty->term->flags|tty->term_flags) & TERM_RGBCOLOURS) return; + colour_split_rgb(gc->fg, &r, &g, &b); + gc->fg = colour_find_rgb(r, g, b); } /* How many colours does this terminal have? */ @@ -2436,11 +2435,10 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Is this a 24-bit colour? */ if (gc->bg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if (!tty_term_has(tty->term, TTYC_SETRGBB)) { - colour_split_rgb(gc->bg, &r, &g, &b); - gc->bg = colour_find_rgb(r, g, b); - } else + if ((tty->term->flags|tty->term_flags) & TERM_RGBCOLOURS) return; + colour_split_rgb(gc->bg, &r, &g, &b); + gc->bg = colour_find_rgb(r, g, b); } /* How many colours does this terminal have? */ @@ -2617,15 +2615,14 @@ tty_try_colour(struct tty *tty, int colour, const char *type) } if (colour & COLOUR_FLAG_RGB) { + colour_split_rgb(colour & 0xffffff, &r, &g, &b); if (*type == '3') { if (!tty_term_has(tty->term, TTYC_SETRGBF)) - return (-1); - colour_split_rgb(colour & 0xffffff, &r, &g, &b); + goto fallback_rgb; tty_putcode3(tty, TTYC_SETRGBF, r, g, b); } else { if (!tty_term_has(tty->term, TTYC_SETRGBB)) - return (-1); - colour_split_rgb(colour & 0xffffff, &r, &g, &b); + goto fallback_rgb; tty_putcode3(tty, TTYC_SETRGBB, r, g, b); } return (0); @@ -2638,6 +2635,12 @@ fallback_256: log_debug("%s: 256 colour fallback: %s", tty->client->name, s); tty_puts(tty, s); return (0); + +fallback_rgb: + xsnprintf(s, sizeof s, "\033[%s;2;%d;%d;%dm", type, r, g, b); + log_debug("%s: RGB colour fallback: %s", tty->client->name, s); + tty_puts(tty, s); + return (0); } static void From e3887022604d6ee6f922cdaa274b54cdf06020ba Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 13:10:14 +0000 Subject: [PATCH 35/40] Ignore empty commands rather than adding them to the command list rather than trying to skip them later, fixes problem reported by M Kelly. --- cmd-parse.y | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd-parse.y b/cmd-parse.y index 41bcdaa8..2375370b 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -341,7 +341,8 @@ commands : command struct cmd_parse_state *ps = &parse_state; $$ = cmd_parse_new_commands(); - if (ps->scope == NULL || ps->scope->flag) + if ($1->name != NULL && + (ps->scope == NULL || ps->scope->flag)) TAILQ_INSERT_TAIL($$, $1, entry); else cmd_parse_free_command($1); @@ -360,7 +361,8 @@ commands : command { struct cmd_parse_state *ps = &parse_state; - if (ps->scope == NULL || ps->scope->flag) { + if ($3->name != NULL && + (ps->scope == NULL || ps->scope->flag)) { $$ = $1; TAILQ_INSERT_TAIL($$, $3, entry); } else { @@ -641,8 +643,6 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds, * command list. */ TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) { - if (cmd->name == NULL) - continue; alias = cmd_get_alias(cmd->name); if (alias == NULL) continue; @@ -688,8 +688,6 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds, */ result = cmd_list_new(); TAILQ_FOREACH(cmd, cmds, entry) { - if (cmd->name == NULL) - continue; log_debug("%s: %u %s", __func__, cmd->line, cmd->name); cmd_log_argv(cmd->argc, cmd->argv, __func__); From b905c5d455b354f7210dc118f748a658f0358cd7 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 28 Jan 2020 13:23:24 +0000 Subject: [PATCH 36/40] If ALL clients are readonly, allow them to affect the size, suggested by Thomas Sattler. --- resize.c | 16 ++++++++++++++++ tmux.h | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/resize.c b/resize.c index b4142a70..96d733f0 100644 --- a/resize.c +++ b/resize.c @@ -66,10 +66,26 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) static int ignore_client_size(struct client *c) { + struct client *loop; + if (c->session == NULL) return (1); if (c->flags & CLIENT_NOSIZEFLAGS) return (1); + if (c->flags & CLIENT_READONLY) { + /* + * Ignore readonly clients if there are any attached clients + * that aren't readonly. + */ + TAILQ_FOREACH (loop, &clients, entry) { + if (loop->session == NULL) + continue; + if (loop->flags & CLIENT_NOSIZEFLAGS) + continue; + if (~loop->flags & CLIENT_READONLY) + return (1); + } + } if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED)) return (1); return (0); diff --git a/tmux.h b/tmux.h index 3ec89019..418f0a44 100644 --- a/tmux.h +++ b/tmux.h @@ -1590,8 +1590,7 @@ struct client { #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ - CLIENT_DETACHING| \ - CLIENT_READONLY) + CLIENT_DETACHING) int flags; struct key_table *keytable; From 7f3feb1896ffde7bca70ffa8db933196af7b2f91 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 28 Jan 2020 15:52:04 +0000 Subject: [PATCH 37/40] Add to CHANGES. --- CHANGES | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ae980bb0..d6806e49 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,14 @@ CHANGES FROM 3.0 to X.X +* Add support for adding a note to a key binding (with bind-key -N) and use + this to add descriptions to the default key bindings. A new -N flag to + list-keys shows key bindings with notes. Change the default ? binding to use + this to show a readable summary of keys. Also extend command-prompt to return + the name of the key pressed and add a default binding (/) to show the note + for the next key pressed. + +* Add support for the iTerm2 DSR 1337 sequence to get the terminal version. + * Treat plausible but invalid keys (like C-BSpace) as literal like any other unrecognised string passed to send-keys @@ -12,7 +21,8 @@ CHANGES FROM 3.0 to X.X * Document client exit messages in the manual page. -* Do not let read-only clients limit the size. +* Do not let read-only clients limit the size, unless all clients are + read-only. * Add a number of new formats to inspect what sessions and clients a window is present or active in. From 7a15d10bf4abb28e0a85e0090de9c13f80f83aaa Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 29 Jan 2020 08:28:17 +0000 Subject: [PATCH 38/40] Remove extra Pp (from jmc) and add a missing word. --- tmux.1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tmux.1 b/tmux.1 index bc24c193..21510722 100644 --- a/tmux.1 +++ b/tmux.1 @@ -214,7 +214,6 @@ was given) and off. Report the .Nm version. -.Pp .It Ar command Op Ar flags This specifies one of a set of commands used to control .Nm , @@ -4953,7 +4952,7 @@ When the option is reached, the oldest automatically named buffer is deleted. Explicitly named buffers are not subject to .Ic buffer-limit -and may be deleted with +and may be deleted with the .Ic delete-buffer command. .Pp From 531daba584f55fed87ec87986a7f0b497c14626f Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 29 Jan 2020 15:07:49 +0000 Subject: [PATCH 39/40] Do not send DA and DSR again if already have a response. --- input.c | 1 + tty.c | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index d7071bfe..ea9320d1 100644 --- a/input.c +++ b/input.c @@ -773,6 +773,7 @@ input_save_state(struct input_ctx *ictx) ictx->old_mode = s->mode; } +/* Restore screen state. */ static void input_restore_state(struct input_ctx *ictx) { diff --git a/tty.c b/tty.c index 54c3be30..3bab556d 100644 --- a/tty.c +++ b/tty.c @@ -338,8 +338,10 @@ tty_start_tty(struct tty *tty) tty->flags |= TTY_FOCUS; tty_puts(tty, "\033[?1004h"); } - tty_puts(tty, "\033[c\033[1337n"); /* DA and DSR */ - + if (~tty->flags & TTY_HAVEDA) + tty_puts(tty, "\033[c"); + if (~tty->flags & TTY_HAVEDSR) + tty_puts(tty, "\033[1337n"); } else tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); From 44dad918f82124272486dfd4439dd72cb969fa12 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 29 Jan 2020 16:22:32 +0000 Subject: [PATCH 40/40] Warn if a message type that is no longer used is received. --- client.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client.c b/client.c index a36c6471..0c2a6ee2 100644 --- a/client.c +++ b/client.c @@ -863,6 +863,12 @@ client_dispatch_wait(struct imsg *imsg) case MSG_WRITE_CLOSE: client_write_close(data, datalen); break; + case MSG_OLDSTDERR: + case MSG_OLDSTDIN: + case MSG_OLDSTDOUT: + fprintf(stderr, "server version is too old for client\n"); + proc_exit(client_proc); + break; } }