From 9d83c5e94827db0248226149cffee23296c851aa Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 3 Nov 2020 08:09:35 +0000 Subject: [PATCH 01/90] Expand menu and popup -x and -y as a format, from teo_paul1 at yahoo dot com in GitHub issue 2442. --- cmd-display-menu.c | 254 ++++++++++++++++++++++++++++++--------------- tmux.1 | 22 ++++ 2 files changed, 190 insertions(+), 86 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 205d1243..7115dc81 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -61,7 +61,7 @@ const struct cmd_entry cmd_display_popup_entry = { .exec = cmd_display_popup_exec }; -static void +static int cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, struct args *args, u_int *px, u_int *py, u_int w, u_int h) { @@ -71,44 +71,46 @@ cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, struct session *s = tc->session; struct winlink *wl = target->wl; struct window_pane *wp = target->wp; - struct style_ranges *ranges; - struct style_range *sr; + struct style_ranges *ranges = NULL; + struct style_range *sr = NULL; const char *xp, *yp; - u_int line, ox, oy, sx, sy, lines; + char *p; + int top; + u_int line, ox, oy, sx, sy, lines, position; + long n; + struct format_tree *ft; - lines = status_line_size(tc); - for (line = 0; line < lines; line++) { - ranges = &tc->status.entries[line].ranges; - TAILQ_FOREACH(sr, ranges, entry) { - if (sr->type == STYLE_RANGE_WINDOW) - break; - } - if (sr != NULL) - break; + /* + * Work out the position from the -x and -y arguments. This is the + * bottom-left position. + */ + + /* If the popup is too big, stop now. */ + if (w > tty->sx || h > tty->sy) + return (0); + + /* Create format with mouse position if any. */ + ft = format_create_from_target(item); + if (event->m.valid) { + format_add(ft, "popup_mouse_x", "%u", event->m.x); + format_add(ft, "popup_mouse_y", "%u", event->m.y); } - if (line == lines) - ranges = &tc->status.entries[0].ranges; - xp = args_get(args, 'x'); - if (xp == NULL || strcmp(xp, "C") == 0) - *px = (tty->sx - 1) / 2 - w / 2; - else if (strcmp(xp, "R") == 0) - *px = tty->sx - 1; - else if (strcmp(xp, "P") == 0) { - tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy); - if (wp->xoff >= ox) - *px = wp->xoff - ox; + /* + * If there are any status lines, add this window position and the + * status line position. + */ + top = status_at_line(tc); + if (top != -1) { + lines = status_line_size(tc); + if (top == 0) + top = lines; else - *px = 0; - } else if (strcmp(xp, "M") == 0) { - if (event->m.valid && event->m.x > w / 2) - *px = event->m.x - w / 2; - else - *px = 0; - } else if (strcmp(xp, "W") == 0) { - if (status_at_line(tc) == -1) - *px = 0; - else { + top = 0; + position = options_get_number(s->options, "status-position"); + + for (line = 0; line < lines; line++) { + ranges = &tc->status.entries[line].ranges; TAILQ_FOREACH(sr, ranges, entry) { if (sr->type != STYLE_RANGE_WINDOW) continue; @@ -116,61 +118,137 @@ cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, break; } if (sr != NULL) - *px = sr->start; - else - *px = 0; + break; + } + if (line == lines) + ranges = &tc->status.entries[0].ranges; + + if (sr != NULL) { + format_add(ft, "popup_window_status_line_x", "%u", + sr->start); + if (position == 0) { + format_add(ft, "popup_window_status_line_y", + "%u", line + 1 + h); + } else { + format_add(ft, "popup_window_status_line_y", + "%u", tty->sy - lines + line); + } + } + + if (position == 0) + format_add(ft, "popup_status_line_y", "%u", lines + h); + else { + format_add(ft, "popup_status_line_y", "%u", + tty->sy - lines); } } else - *px = strtoul(xp, NULL, 10); - if ((*px) + w >= tty->sx) - *px = tty->sx - w; + top = 0; + /* Popup width and height. */ + format_add(ft, "popup_width", "%u", w); + format_add(ft, "popup_height", "%u", h); + + /* Position so popup is in the centre. */ + n = (long)(tty->sx - 1) / 2 - w / 2; + if (n < 0) + format_add(ft, "popup_centre_x", "%u", 0); + else + format_add(ft, "popup_centre_x", "%ld", n); + n = (tty->sy - 1) / 2 + h / 2; + if (n + h >= tty->sy) + format_add(ft, "popup_centre_y", "%u", tty->sy - h); + else + format_add(ft, "popup_centre_y", "%ld", n); + + /* Position of popup relative to mouse. */ + if (event->m.valid) { + n = (long)event->m.x - w / 2; + if (n < 0) + format_add(ft, "popup_mouse_centre_x", "%u", 0); + else + format_add(ft, "popup_mouse_centre_x", "%ld", n); + n = event->m.y - h / 2; + if (n + h >= tty->sy) { + format_add(ft, "popup_mouse_centre_y", "%u", + tty->sy - h); + } else + format_add(ft, "popup_mouse_centre_y", "%ld", n); + n = (long)event->m.y + h; + if (n + h >= tty->sy) + format_add(ft, "popup_mouse_top", "%u", tty->sy - h); + else + format_add(ft, "popup_mouse_top", "%ld", n); + n = event->m.y - h; + if (n < 0) + format_add(ft, "popup_mouse_bottom", "%u", 0); + else + format_add(ft, "popup_mouse_bottom", "%ld", n); + } + + /* Position in pane. */ + tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy); + n = top + wp->yoff - oy + h; + if (n >= tty->sy) + format_add(ft, "popup_pane_top", "%u", tty->sy - h); + else + format_add(ft, "popup_pane_top", "%ld", n); + format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy); + format_add(ft, "popup_pane_left", "%u", wp->xoff - ox); + n = (long)wp->xoff + wp->sx - ox - w; + if (n < 0) + format_add(ft, "popup_pane_right", "%u", 0); + else + format_add(ft, "popup_pane_right", "%ld", n); + + /* Expand horizontal position. */ + xp = args_get(args, 'x'); + if (xp == NULL || strcmp(xp, "C") == 0) + xp = "#{popup_centre_x}"; + else if (strcmp(xp, "R") == 0) + xp = "#{popup_right}"; + else if (strcmp(xp, "P") == 0) + xp = "#{popup_pane_left}"; + else if (strcmp(xp, "M") == 0) + xp = "#{popup_mouse_centre_x}"; + else if (strcmp(xp, "W") == 0) + xp = "#{popup_window_status_line_x}"; + p = format_expand(ft, xp); + n = strtol(p, NULL, 10); + if (n + w >= tty->sx) + n = tty->sx - w; + else if (n < 0) + n = 0; + *px = n; + log_debug("%s: -x: %s = %s = %u", __func__, xp, p, *px); + free(p); + + /* Expand vertical position */ yp = args_get(args, 'y'); if (yp == NULL || strcmp(yp, "C") == 0) - *py = (tty->sy - 1) / 2 + h / 2; - else if (strcmp(yp, "P") == 0) { - tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy); - if (wp->yoff + wp->sy >= oy) - *py = wp->yoff + wp->sy - oy; - else - *py = 0; - } else if (strcmp(yp, "M") == 0) { - if (event->m.valid) - *py = event->m.y + h; - else - *py = 0; - } else if (strcmp(yp, "S") == 0) { - if (options_get_number(s->options, "status-position") == 0) { - if (lines != 0) - *py = lines + h; - else - *py = 0; - } else { - if (lines != 0) - *py = tty->sy - lines; - else - *py = tty->sy; - } - } else if (strcmp(yp, "W") == 0) { - if (options_get_number(s->options, "status-position") == 0) { - if (lines != 0) - *py = line + 1 + h; - else - *py = 0; - } else { - if (lines != 0) - *py = tty->sy - lines + line; - else - *py = tty->sy; - } - } else - *py = strtoul(yp, NULL, 10); - if (*py < h) - *py = 0; + yp = "#{popup_centre_y}"; + else if (strcmp(yp, "P") == 0) + yp = "#{popup_pane_bottom}"; + else if (strcmp(yp, "M") == 0) + yp = "#{popup_mouse_top}"; + else if (strcmp(yp, "S") == 0) + yp = "#{popup_status_line_y}"; + else if (strcmp(yp, "W") == 0) + yp = "#{popup_window_status_line_y}"; + p = format_expand(ft, yp); + n = strtol(p, NULL, 10); + if (n < h) + n = 0; else - *py -= h; - if ((*py) + h >= tty->sy) - *py = tty->sy - h; + n -= h; + if (n + h >= tty->sy) + n = tty->sy - h; + else if (n < 0) + n = 0; + *py = n; + log_debug("%s: -y: %s = %s = %u", __func__, yp, p, *py); + free(p); + + return (1); } static enum cmd_retval @@ -226,8 +304,11 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) menu_free(menu); return (CMD_RETURN_NORMAL); } - cmd_display_menu_get_position(tc, item, args, &px, &py, menu->width + 4, - menu->count + 2); + if (!cmd_display_menu_get_position(tc, item, args, &px, &py, + menu->width + 4, menu->count + 2)) { + menu_free(menu); + return (CMD_RETURN_NORMAL); + } if (args_has(args, 'O')) flags |= MENU_STAYOPEN; @@ -296,7 +377,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) w = tty->sx - 1; if (h > tty->sy - 1) h = tty->sy - 1; - cmd_display_menu_get_position(tc, item, args, &px, &py, w, h); + if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h)) + return (CMD_RETURN_NORMAL); value = args_get(args, 'd'); if (value != NULL) diff --git a/tmux.1 b/tmux.1 index 3d58b32f..0a71cc37 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5388,6 +5388,28 @@ Both may be a row or column number, or one of the following special values: .It Li "S" Ta Fl y Ta "The line above or below the status line" .El .Pp +Or a format, which is expanded including the following additional variables: +.Bl -column "XXXXXXXXXXXXXXXXXXXXXXXXXX" -offset indent +.It Sy "Variable name" Ta Sy "Replaced with" +.It Li "popup_centre_x" Ta "Centered in the client" +.It Li "popup_centre_y" Ta "Centered in the client" +.It Li "popup_height" Ta "Height of menu or popup" +.It Li "popup_mouse_bottom" Ta "Bottom of at the mouse" +.It Li "popup_mouse_centre_x" Ta "Horizontal centre at the mouse" +.It Li "popup_mouse_centre_y" Ta "Vertical centre at the mouse" +.It Li "popup_mouse_top" Ta "Top at the mouse" +.It Li "popup_mouse_x" Ta "Mouse X position" +.It Li "popup_mouse_y" Ta "Mouse Y position" +.It Li "popup_pane_bottom" Ta "Bottom of the pane" +.It Li "popup_pane_left" Ta "Left of the pane" +.It Li "popup_pane_right" Ta "Right of the pane" +.It Li "popup_pane_top" Ta "Top of the pane" +.It Li "popup_status_line_y" Ta "Above or below the status line" +.It Li "popup_width" Ta "Width of menu or popup" +.It Li "popup_window_status_line_x" Ta "At the window position in status line" +.It Li "popup_window_status_line_y" Ta "At the status line showing the window" +.El +.Pp Each menu consists of items followed by a key shortcut shown in brackets. If the menu is too large to fit on the terminal, it is not displayed. Pressing the key shortcut chooses the corresponding item. From 572a6b21b5a4d4e9587ddfc933449bb89fafeab1 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 3 Nov 2020 08:41:24 +0000 Subject: [PATCH 02/90] Back to 3.3. --- CHANGES | 4 ++++ configure.ac | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6f3944c7..8a438f81 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +CHANGES FROM 3.2 TO 3.3 + +XXX + CHANGES FROM 3.1c TO 3.2 * Fire focus events even when the pane is in a mode. diff --git a/configure.ac b/configure.ac index 768d3db3..93246fc8 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.2-rc3) +AC_INIT([tmux], next-3.3) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) From 1326529f99366e7d438364fd8727ec132a2f7433 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 9 Nov 2020 08:42:43 +0000 Subject: [PATCH 03/90] Remove some old debug logging. --- screen-write.c | 1 - 1 file changed, 1 deletion(-) diff --git a/screen-write.c b/screen-write.c index c9c61086..ab6a020c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1551,7 +1551,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { screen_write_set_cursor(ctx, ci->x, y); if (ci->type == CLEAR_END) { - log_debug("XXX %u %u", ci->x, ci->bg); screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; tty_write(tty_cmd_clearendofline, &ttyctx); From 72c46aa15e50ef6391ab3fe6e230ed3abc9485b0 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 9 Nov 2020 09:00:41 +0000 Subject: [PATCH 04/90] Add support for Haiku, from David Carlier. GitHub issue 2453. --- Makefile.am | 5 +++ compat.h | 4 +++ compat/forkpty-haiku.c | 82 ++++++++++++++++++++++++++++++++++++++++++ configure.ac | 30 ++++++++++++---- osdep-haiku.c | 53 +++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 compat/forkpty-haiku.c create mode 100644 osdep-haiku.c diff --git a/Makefile.am b/Makefile.am index 91d641fd..7b84b1e9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -58,6 +58,11 @@ if IS_NETBSD AM_CPPFLAGS += -D_OPENBSD_SOURCE endif +# Set flags for Haiku. +if IS_HAIKU +AM_CPPFLAGS += -D_BSD_SOURCE +endif + # List of sources. dist_tmux_SOURCES = \ alerts.c \ diff --git a/compat.h b/compat.h index b213336f..ec125ced 100644 --- a/compat.h +++ b/compat.h @@ -110,6 +110,10 @@ void warnx(const char *, ...); #define pledge(s, p) (0) #endif +#ifndef IMAXBEL +#define IMAXBEL 0 +#endif + #ifdef HAVE_STDINT_H #include #else diff --git a/compat/forkpty-haiku.c b/compat/forkpty-haiku.c new file mode 100644 index 00000000..6112164c --- /dev/null +++ b/compat/forkpty-haiku.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2008 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include + +#include "compat.h" + +void fatal(const char *, ...); +void fatalx(const char *, ...); + +pid_t +forkpty(int *master, char *name, struct termios *tio, struct winsize *ws) +{ + int slave = -1; + char *path; + pid_t pid; + + if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1) + return (-1); + if (grantpt(*master) != 0) + goto out; + if (unlockpt(*master) != 0) + goto out; + + if ((path = ptsname(*master)) == NULL) + goto out; + if (name != NULL) + strlcpy(name, path, TTY_NAME_MAX); + if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1) + goto out; + + switch (pid = fork()) { + case -1: + goto out; + case 0: + close(*master); + + setsid(); + if (ioctl(slave, TIOCSCTTY, NULL) == -1) + fatal("ioctl failed"); + + if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1) + fatal("tcsetattr failed"); + if (ioctl(slave, TIOCSWINSZ, ws) == -1) + fatal("ioctl failed"); + + dup2(slave, 0); + dup2(slave, 1); + dup2(slave, 2); + if (slave > 2) + close(slave); + return (0); + } + + close(slave); + return (pid); + +out: + if (*master != -1) + close(*master); + if (slave != -1) + close(slave); + return (-1); +} diff --git a/configure.ac b/configure.ac index 93246fc8..cf621835 100644 --- a/configure.ac +++ b/configure.ac @@ -296,12 +296,25 @@ AC_TRY_LINK( found_b64_ntop=yes, found_b64_ntop=no ) +OLD_LIBS="$LIBS" if test "x$found_b64_ntop" = xno; then AC_MSG_RESULT(no) - AC_MSG_CHECKING(for b64_ntop with -lresolv) - OLD_LIBS="$LIBS" - LIBS="$LIBS -lresolv" + LIBS="$OLD_LIBS -lresolv" + AC_TRY_LINK( + [ + #include + #include + #include + ], + [b64_ntop(NULL, 0, NULL, 0);], + found_b64_ntop=yes, + found_b64_ntop=no + ) +fi +if test "x$found_b64_ntop" = xno; then + AC_MSG_CHECKING(for b64_ntop with -lnetwork) + LIBS="$OLD_LIBS -lnetwork" AC_TRY_LINK( [ #include @@ -312,16 +325,14 @@ if test "x$found_b64_ntop" = xno; then found_b64_ntop=yes, found_b64_ntop=no ) - if test "x$found_b64_ntop" = xno; then - LIBS="$OLD_LIBS" - AC_MSG_RESULT(no) - fi fi if test "x$found_b64_ntop" = xyes; then AC_DEFINE(HAVE_B64_NTOP) AC_MSG_RESULT(yes) else + LIBS="$OLD_LIBS" AC_LIBOBJ(base64) + AC_MSG_RESULT(no) fi # Look for networking libraries. @@ -656,6 +667,10 @@ case "$host_os" in AC_MSG_RESULT(cygwin) PLATFORM=cygwin ;; + *haiku*) + AC_MSG_RESULT(haiku) + PLATFORM=haiku + ;; *) AC_MSG_RESULT(unknown) PLATFORM=unknown @@ -671,6 +686,7 @@ AM_CONDITIONAL(IS_NETBSD, test "x$PLATFORM" = xnetbsd) AM_CONDITIONAL(IS_OPENBSD, test "x$PLATFORM" = xopenbsd) AM_CONDITIONAL(IS_SUNOS, test "x$PLATFORM" = xsunos) AM_CONDITIONAL(IS_HPUX, test "x$PLATFORM" = xhpux) +AM_CONDITIONAL(IS_HAIKU, test "x$PLATFORM" = xhaiku) AM_CONDITIONAL(IS_UNKNOWN, test "x$PLATFORM" = xunknown) # Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user diff --git a/osdep-haiku.c b/osdep-haiku.c new file mode 100644 index 00000000..298dc05e --- /dev/null +++ b/osdep-haiku.c @@ -0,0 +1,53 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "tmux.h" + +char * +osdep_get_name(int fd, __unused char *tty) +{ + pid_t tid; + team_info tinfo; + + if ((tid = tcgetpgrp(fd)) == -1) + return (NULL); + + if (get_team_info(tid, &tinfo) != B_OK) + return (NULL); + + /* Up to the first 64 characters. */ + return (xstrdup(tinfo.args)); +} + +char * +osdep_get_cwd(int fd) +{ + return (NULL); +} + +struct event_base * +osdep_event_init(void) +{ + return (event_init()); +} From 61e55fa50dc7119a8c88bb385e615a8e6c8d6a85 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 9 Nov 2020 09:10:10 +0000 Subject: [PATCH 05/90] Change how escaping is processed for formats so that ## and # can be used in styles. Also add a 'w' format modifier for the width. From Chas J Owens IV in GitHub issue 2389. --- format-draw.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++---- format.c | 40 ++++++++++-- tmux.1 | 4 +- 3 files changed, 201 insertions(+), 17 deletions(-) diff --git a/format-draw.c b/format-draw.c index ec98ba95..e73c5df4 100644 --- a/format-draw.c +++ b/format-draw.c @@ -486,6 +486,18 @@ format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, focus_end, frs); } +/* Draw multiple characters. */ +static void +format_draw_many(struct screen_write_ctx *ctx, struct style *sy, char ch, + u_int n) +{ + u_int i; + + utf8_set(&sy->gc.data, ch); + for (i = 0; i < n; i++) + screen_write_cell(ctx, &sy->gc); +} + /* Draw a format to a screen. */ void format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, @@ -509,10 +521,10 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, size_t size = strlen(expanded); struct screen *os = octx->s, s[TOTAL]; struct screen_write_ctx ctx[TOTAL]; - u_int ocx = os->cx, ocy = os->cy, i, width[TOTAL]; + u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL]; u_int map[] = { LEFT, LEFT, CENTRE, RIGHT }; int focus_start = -1, focus_end = -1; - int list_state = -1, fill = -1; + int list_state = -1, fill = -1, even; enum style_align list_align = STYLE_ALIGN_DEFAULT; struct grid_cell gc, current_default; struct style sy, saved_sy; @@ -547,6 +559,34 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ cp = expanded; while (*cp != '\0') { + /* Handle sequences of #. */ + if (cp[0] == '#' && cp[1] != '[' && cp[1] != '\0') { + for (n = 1; cp[n] == '#'; n++) + /* nothing */; + if (cp[n] != '[') { + width[current] += n; + cp += n; + format_draw_many(&ctx[current], &sy, '#', n); + continue; + } + even = ((n % 2) == 0); + if (even) + cp += (n + 1); + else + cp += (n - 1); + if (sy.ignore) + continue; + format_draw_many(&ctx[current], &sy, '#', n / 2); + width[current] += (n / 2); + if (even) { + utf8_set(ud, '['); + screen_write_cell(&ctx[current], &sy.gc); + width[current]++; + } + continue; + } + + /* Is this not a style? */ if (cp[0] != '#' || cp[1] != '[' || sy.ignore) { /* See if this is a UTF-8 character. */ if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { @@ -796,13 +836,33 @@ u_int format_width(const char *expanded) { const char *cp, *end; - u_int width = 0; + u_int n, width = 0; struct utf8_data ud; enum utf8_state more; cp = expanded; while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { + if (*cp == '#') { + for (n = 1; cp[n] == '#'; n++) + /* nothing */; + if (cp[n] != '[') { + width += n; + cp += n; + continue; + } + width += (n / 2); /* one for each ## */ + + if ((n % 2) == 0) { + /* + * An even number of #s means that all #s are + * escaped, so not a style. + */ + width++; /* one for the [ */ + cp += (n + 1); + continue; + } + cp += (n - 1); /* point to the [ */ + end = format_skip(cp + 2, "]"); if (end == NULL) return (0); @@ -823,19 +883,57 @@ format_width(const char *expanded) return (width); } -/* Trim on the left, taking #[] into account. */ +/* + * Trim on the left, taking #[] into account. Note, we copy the whole set of + * unescaped #s, but only add their escaped size to width. This is because the + * format_draw function will actually do the escaping when it runs + */ char * format_trim_left(const char *expanded, u_int limit) { char *copy, *out; const char *cp = expanded, *end; - u_int width = 0; + u_int even, n, width = 0; struct utf8_data ud; enum utf8_state more; - out = copy = xmalloc(strlen(expanded) + 1); + out = copy = xcalloc(1, strlen(expanded) + 1); while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { + if (width >= limit) + break; + if (*cp == '#') { + for (end = cp + 1; *end == '#'; end++) + /* nothing */; + n = end - cp; + if (*end != '[') { + if (n > limit - width) + n = limit - width; + memcpy(out, cp, n); + out += n; + width += n; + cp = end; + continue; + } + even = ((n % 2) == 0); + + n /= 2; + if (n > limit - width) + n = limit - width; + width += n; + n *= 2; + memcpy(out, cp, n); + out += n; + + if (even) { + if (width + 1 <= limit) { + *out++ = '['; + width++; + } + cp = end + 1; + continue; + } + cp = end - 1; + end = format_skip(cp + 2, "]"); if (end == NULL) break; @@ -873,7 +971,7 @@ format_trim_right(const char *expanded, u_int limit) { char *copy, *out; const char *cp = expanded, *end; - u_int width = 0, total_width, skip; + u_int width = 0, total_width, skip, old_n, even, n; struct utf8_data ud; enum utf8_state more; @@ -882,12 +980,64 @@ format_trim_right(const char *expanded, u_int limit) return (xstrdup(expanded)); skip = total_width - limit; - out = copy = xmalloc(strlen(expanded) + 1); + out = copy = xcalloc(1, strlen(expanded) + 1); while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { + if (*cp == '#') { + for (end = cp + 1; *end == '#'; end++) + /* nothing */; + old_n = n = end - cp; + if (*end != '[') { + if (width <= skip) { + if (skip - width >= n) + n = 0; + else + n -= (skip - width); + } + if (n != 0) { + memcpy(out, cp, n); + out += n; + } + + /* + * The width always increases by the full + * amount even if we can't copy anything yet. + */ + width += old_n; + cp = end; + continue; + } + even = ((n % 2) == 0); + + n /= 2; + if (width <= skip) { + if (skip - width >= n) + n = 0; + else + n -= (skip - width); + } + if (n != 0) { + /* + * Copy the full amount because it hasn't been + * escaped yet. + */ + memcpy(out, cp, old_n); + out += old_n; + } + cp += old_n; + width += (old_n / 2) - even; + + if (even) { + if (width > skip) + *out++ = '['; + width++; + continue; + } + cp = end - 1; + end = format_skip(cp + 2, "]"); - if (end == NULL) + if (end == NULL) { break; + } memcpy(out, cp, end + 1 - cp); out += (end + 1 - cp); cp = end + 1; diff --git a/format.c b/format.c index 99efbc1c..91955259 100644 --- a/format.c +++ b/format.c @@ -98,6 +98,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_PANES 0x200 #define FORMAT_PRETTY 0x400 #define FORMAT_LENGTH 0x800 +#define FORMAT_WIDTH 0x1000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 10 @@ -1671,7 +1672,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, /* * Modifiers are a ; separated list of the forms: - * l,m,C,b,d,n,t,q,E,T,S,W,P,<,> + * l,m,C,b,d,n,t,w,q,E,T,S,W,P,<,> * =a * =/a * =/a/ @@ -1688,7 +1689,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, cp++; /* Check single character modifiers with no arguments. */ - if (strchr("lbdnqETSWP<>", cp[0]) != NULL && + if (strchr("lbdnqwETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -2184,6 +2185,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, if (errptr != NULL) width = 0; break; + case 'w': + modifiers |= FORMAT_WIDTH; + break; case 'e': if (fm->argc < 1 || fm->argc > 3) break; @@ -2456,13 +2460,19 @@ done: format_log(es, "applied padding width %d: %s", width, value); } - /* Replace with the length if needed. */ + /* Replace with the length or width if needed. */ if (modifiers & FORMAT_LENGTH) { xasprintf(&new, "%zu", strlen(value)); free(value); value = new; format_log(es, "replacing with length: %s", new); } + if (modifiers & FORMAT_WIDTH) { + xasprintf(&new, "%u", format_width(value)); + free(value); + value = new; + format_log(es, "replacing with width: %s", new); + } /* Expand the buffer and copy in the value. */ valuelen = strlen(value); @@ -2589,8 +2599,30 @@ format_expand1(struct format_expand_state *es, const char *fmt) break; fmt += n + 1; continue; - case '}': case '#': + /* + * If ##[ (with two or more #s), then it is a style and + * can be left for format_draw to handle. + */ + ptr = fmt; + n = 2; + while (*ptr == '#') { + ptr++; + n++; + } + if (*ptr == '[') { + format_log(es, "found #*%zu[", n); + while (len - off < n + 2) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, fmt - 2, n + 1); + off += n + 1; + fmt = ptr + 1; + continue; + } + /* FALLTHROUGH */ + case '}': case ',': format_log(es, "found #%c", ch); while (len - off < 2) { diff --git a/tmux.1 b/tmux.1 index 0a71cc37..37f6f165 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4591,7 +4591,9 @@ pads the string to a given width, for example will result in a width of at least 10 characters. A positive width pads on the left, a negative on the right. .Ql n -expands to the length of the variable, for example +expands to the length of the variable and +.Ql w +to its width when displayed, for example .Ql #{n:window_name} . .Pp Prefixing a time variable with From f1193b48910aed15a2c73cdb5784a26f2ea0f64c Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 9 Nov 2020 10:54:28 +0000 Subject: [PATCH 06/90] If mouse bits change, clear them all and set again to avoid problems with some bits implying others. GitHub issue 2458. --- tty.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tty.c b/tty.c index bcbccca6..777b639b 100644 --- a/tty.c +++ b/tty.c @@ -694,28 +694,26 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) } if ((changed & ALL_MOUSE_MODES) && tty_term_has(tty->term, TTYC_KMOUS)) { - if ((mode & ALL_MOUSE_MODES) == 0) + /* + * If the mouse modes have changed, clear any that are set and + * apply again. There are differences in how terminals track + * the various bits. + */ + if (tty->mode & MODE_MOUSE_SGR) tty_puts(tty, "\033[?1006l"); - if ((changed & MODE_MOUSE_STANDARD) && - (~mode & MODE_MOUSE_STANDARD)) + if (tty->mode & MODE_MOUSE_STANDARD) tty_puts(tty, "\033[?1000l"); - if ((changed & MODE_MOUSE_BUTTON) && - (~mode & MODE_MOUSE_BUTTON)) + if (tty->mode & MODE_MOUSE_BUTTON) tty_puts(tty, "\033[?1002l"); - if ((changed & MODE_MOUSE_ALL) && - (~mode & MODE_MOUSE_ALL)) + if (tty->mode & MODE_MOUSE_ALL) tty_puts(tty, "\033[?1003l"); - if (mode & ALL_MOUSE_MODES) tty_puts(tty, "\033[?1006h"); - if ((changed & MODE_MOUSE_STANDARD) && - (mode & MODE_MOUSE_STANDARD)) + if (mode & MODE_MOUSE_STANDARD) tty_puts(tty, "\033[?1000h"); - if ((changed & MODE_MOUSE_BUTTON) && - (mode & MODE_MOUSE_BUTTON)) + if (mode & MODE_MOUSE_BUTTON) tty_puts(tty, "\033[?1002h"); - if ((changed & MODE_MOUSE_ALL) && - (mode & MODE_MOUSE_ALL)) + if (mode & MODE_MOUSE_ALL) tty_puts(tty, "\033[?1003h"); } if (changed & MODE_BRACKETPASTE) { From 3eb1519bd784076f63fed6678b88f918317a2124 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 9 Nov 2020 16:41:55 +0000 Subject: [PATCH 07/90] Scaffold for oss-fuzz, from Sergey Nizovtsev. --- .gitignore | 2 + Makefile.am | 6 +++ compat.h | 8 ++++ configure.ac | 47 +++++++++++++++++++-- fuzz/input-fuzzer.c | 89 +++++++++++++++++++++++++++++++++++++++ fuzz/input-fuzzer.dict | 8 ++++ fuzz/input-fuzzer.options | 2 + 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 fuzz/input-fuzzer.c create mode 100644 fuzz/input-fuzzer.dict create mode 100644 fuzz/input-fuzzer.options diff --git a/.gitignore b/.gitignore index d01a0166..ec49a6de 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ configure tmux.1.* *.dSYM cmd-parse.c +fuzz/*-fuzzer +.dirstamp diff --git a/Makefile.am b/Makefile.am index 7b84b1e9..b40e0944 100644 --- a/Makefile.am +++ b/Makefile.am @@ -202,6 +202,12 @@ if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c endif +if NEED_FUZZING +check_PROGRAMS = fuzz/input-fuzzer +fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) +fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) +endif + # Install tmux.1 in the right format. install-exec-hook: if test x@MANFORMAT@ = xmdoc; then \ diff --git a/compat.h b/compat.h index ec125ced..6e26a02c 100644 --- a/compat.h +++ b/compat.h @@ -52,6 +52,9 @@ #ifndef __packed #define __packed __attribute__ ((__packed__)) #endif +#ifndef __weak +#define __weak __attribute__ ((__weak__)) +#endif #ifndef ECHOPRT #define ECHOPRT 0 @@ -395,6 +398,11 @@ int utf8proc_mbtowc(wchar_t *, const char *, size_t); int utf8proc_wctomb(char *, wchar_t); #endif +#ifdef NEED_FUZZING +/* tmux.c */ +#define main __weak main +#endif + /* getopt.c */ extern int BSDopterr; extern int BSDoptind; diff --git a/configure.ac b/configure.ac index cf621835..97010df4 100644 --- a/configure.ac +++ b/configure.ac @@ -21,6 +21,26 @@ SAVED_CFLAGS="$CFLAGS" SAVED_CPPFLAGS="$CPPFLAGS" SAVED_LDFLAGS="$LDFLAGS" +# Is this oss-fuzz build? +AC_ARG_ENABLE( + fuzzing, + AC_HELP_STRING(--enable-fuzzing, build fuzzers) +) +AC_ARG_VAR( + FUZZING_LIBS, + AC_HELP_STRING(libraries to link fuzzing targets with) +) + +# Set up convenient fuzzing defaults before initializing compiler. +if test "x$enable_fuzzing" = xyes; then + AC_DEFINE(NEED_FUZZING) + test "x$CC" == x && CC=clang + test "x$FUZZING_LIBS" == x && \ + FUZZING_LIBS="-fsanitize=fuzzer" + test "x$SAVED_CFLAGS" == x && \ + AM_CFLAGS="-g -fsanitize=fuzzer-no-link,address" +fi + # Set up the compiler in two different ways and say yes we may want to install. AC_PROG_CC AM_PROG_CC_C_O @@ -54,8 +74,11 @@ if test "x$enable_static" = xyes; then LDFLAGS="$AM_LDFLAGS $SAVED_LDFLAGS" fi +# Do we need fuzzers? +AM_CONDITIONAL(NEED_FUZZING, test "x$enable_fuzzing" = xyes) + # Is this gcc? -AM_CONDITIONAL(IS_GCC, test "x$GCC" = xyes) +AM_CONDITIONAL(IS_GCC, test "x$GCC" = xyes -a "x$enable_fuzzing" != xyes) # Is this Sun CC? AC_EGREP_CPP( @@ -117,8 +140,6 @@ AC_REPLACE_FUNCS([ \ getline \ getprogname \ memmem \ - recallocarray \ - reallocarray \ setenv \ setproctitle \ strcasestr \ @@ -130,6 +151,26 @@ AC_REPLACE_FUNCS([ \ ]) AC_FUNC_STRNLEN +# Clang sanitizers wrap reallocarray even if it isn't available on the target +# system. When compiled it always returns NULL and crashes the program. To +# detect this we need a more complicated test. +AC_MSG_CHECKING([for working reallocarray]) +AC_RUN_IFELSE([AC_LANG_PROGRAM( + [#include ], + [return (reallocarray(NULL, 1, 1) == NULL);] + )], + AC_MSG_RESULT(yes), + [AC_LIBOBJ(reallocarray) AC_MSG_RESULT([no])] +) +AC_MSG_CHECKING([for working recallocarray]) +AC_RUN_IFELSE([AC_LANG_PROGRAM( + [#include ], + [return (recallocarray(NULL, 1, 1, 1) == NULL);] + )], + AC_MSG_RESULT(yes), + [AC_LIBOBJ(recallocarray) AC_MSG_RESULT([no])] +) + # Look for clock_gettime. Must come before event_init. AC_SEARCH_LIBS(clock_gettime, rt) diff --git a/fuzz/input-fuzzer.c b/fuzz/input-fuzzer.c new file mode 100644 index 00000000..27f2be3d --- /dev/null +++ b/fuzz/input-fuzzer.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 Sergey Nizovtsev + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "tmux.h" + +#define FUZZER_MAXLEN 512 +#define PANE_WIDTH 80 +#define PANE_HEIGHT 25 + +struct event_base *libevent; + +int +LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) +{ + struct bufferevent *vpty[2]; + struct window *w; + struct window_pane *wp; + int error; + + /* + * Since AFL doesn't support -max_len paramenter we have to + * discard long inputs manually. + */ + if (size > FUZZER_MAXLEN) + return 0; + + w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0); + wp = window_add_pane(w, NULL, 0, 0); + bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty); + wp->ictx = input_init(wp, vpty[0]); + window_add_ref(w, __func__); + + input_parse_buffer(wp, (u_char*) data, size); + while (cmdq_next(NULL) != 0) + ; + error = event_base_loop(libevent, EVLOOP_NONBLOCK); + if (error == -1) + errx(1, "event_base_loop failed"); + + assert(w->references == 1); + window_remove_ref(w, __func__); + + bufferevent_free(vpty[0]); + bufferevent_free(vpty[1]); + + return 0; +} + +int +LLVMFuzzerInitialize(__unused int *argc, __unused char ***argv) +{ + const struct options_table_entry *oe; + + global_environ = environ_create(); + global_options = options_create(NULL); + global_s_options = options_create(NULL); + global_w_options = options_create(NULL); + for (oe = options_table; oe->name != NULL; oe++) { + if (oe->scope & OPTIONS_TABLE_SERVER) + options_default(global_options, oe); + if (oe->scope & OPTIONS_TABLE_SESSION) + options_default(global_s_options, oe); + if (oe->scope & OPTIONS_TABLE_WINDOW) + options_default(global_w_options, oe); + } + libevent = osdep_event_init(); + + options_set_number(global_w_options, "monitor-bell", 0); + options_set_number(global_w_options, "allow-rename", 1); + options_set_number(global_options, "set-clipboard", 2); + + return 0; +} diff --git a/fuzz/input-fuzzer.dict b/fuzz/input-fuzzer.dict new file mode 100644 index 00000000..2091b970 --- /dev/null +++ b/fuzz/input-fuzzer.dict @@ -0,0 +1,8 @@ +"\x1b[" +"1000" +"2004" +"1049" +"38;2" +"100;" +"tmux;" +"rgb:00/00/00" diff --git a/fuzz/input-fuzzer.options b/fuzz/input-fuzzer.options new file mode 100644 index 00000000..5d468bc6 --- /dev/null +++ b/fuzz/input-fuzzer.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 512 From bbab5b7a30717c4455f7725e6adc364f6c274e7d Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Nov 2020 08:16:52 +0000 Subject: [PATCH 08/90] Allow previous-word to scroll onto the first line, noticed by Anindya Mukherjee. --- window-copy.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/window-copy.c b/window-copy.c index 1dc0c293..6dc03b34 100644 --- a/window-copy.c +++ b/window-copy.c @@ -4526,10 +4526,11 @@ window_copy_cursor_previous_word_pos(struct window_mode_entry *wme, const char *separators, int already, u_int *ppx, u_int *ppy) { struct window_copy_mode_data *data = wme->data; - u_int px, py; + u_int px, py, hsize; + hsize = screen_hsize(data->backing); px = data->cx; - py = screen_hsize(data->backing) + data->cy - data->oy; + py = hsize + data->cy - data->oy; /* Move back to the previous word character. */ if (already || window_copy_in_set(wme, px, py, separators)) { @@ -4542,9 +4543,7 @@ window_copy_cursor_previous_word_pos(struct window_mode_entry *wme, } else { if (py == 0 || (data->cy == 0 && - (screen_hsize(data->backing) == 0 || - data->oy >= - screen_hsize(data->backing) - 1))) + (hsize == 0 || data->oy > hsize - 1))) goto out; py--; @@ -4573,10 +4572,11 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, const char *separators, int already) { struct window_copy_mode_data *data = wme->data; - u_int px, py; + u_int px, py, hsize; + hsize = screen_hsize(data->backing); px = data->cx; - py = screen_hsize(data->backing) + data->cy - data->oy; + py = hsize + data->cy - data->oy; /* Move back to the previous word character. */ if (already || window_copy_in_set(wme, px, py, separators)) { @@ -4588,14 +4588,11 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, break; } else { if (data->cy == 0 && - (screen_hsize(data->backing) == 0 || - data->oy >= - screen_hsize(data->backing) - 1)) + (hsize == 0 || data->oy > hsize - 1)) goto out; window_copy_cursor_up(wme, 0); - py = screen_hsize(data->backing) + data->cy - - data->oy; + py = hsize + data->cy - data->oy; px = window_copy_find_length(wme, py); /* Stop if separator at EOL. */ From 0d28ee927421c46315bc0b47b5f8f49a8392efa4 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 17 Nov 2020 08:13:35 +0000 Subject: [PATCH 09/90] Log missing keys when extended keys is on rather than fatal(). --- input-keys.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/input-keys.c b/input-keys.c index 224bcfa5..5cd43dc3 100644 --- a/input-keys.c +++ b/input-keys.c @@ -330,7 +330,8 @@ static struct input_key_entry input_key_defaults[] = { .data = "\033[2;_~" }, { .key = KEYC_DC|KEYC_BUILD_MODIFIERS, - .data = "\033[3;_~" } + .data = "\033[3;_~" + } }; static const key_code input_key_modifiers[] = { 0, @@ -557,7 +558,7 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) modifier = '8'; break; default: - fatalx("invalid key modifiers: %llx", key); + goto missing; } xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier); bufferevent_write(bev, tmp, strlen(tmp)); From bfdc4373d71895a5f454834da4b0bd92ab964dd4 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 17 Nov 2020 17:56:55 +0000 Subject: [PATCH 10/90] Update closefrom from OpenSSH for macOS code which is now needed. --- compat/closefrom.c | 110 ++++++++++++++++++++++++++++++++------------- configure.ac | 4 +- 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/compat/closefrom.c b/compat/closefrom.c index 7915cde4..28be3680 100644 --- a/compat/closefrom.c +++ b/compat/closefrom.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "compat.h" + #ifndef HAVE_CLOSEFROM #include @@ -44,8 +46,9 @@ # include # endif #endif - -#include "compat.h" +#if defined(HAVE_LIBPROC_H) +# include +#endif #ifndef OPEN_MAX # define OPEN_MAX 256 @@ -55,39 +58,15 @@ __unused static const char rcsid[] = "$Sudo: closefrom.c,v 1.11 2006/08/17 15:26:54 millert Exp $"; #endif /* lint */ +#ifndef HAVE_FCNTL_CLOSEM /* * Close all file descriptors greater than or equal to lowfd. */ -#ifdef HAVE_FCNTL_CLOSEM -void -closefrom(int lowfd) +static void +closefrom_fallback(int lowfd) { - (void) fcntl(lowfd, F_CLOSEM, 0); -} -#else -void -closefrom(int lowfd) -{ - long fd, maxfd; -#if defined(HAVE_DIRFD) && defined(HAVE_PROC_PID) - char fdpath[PATH_MAX], *endp; - struct dirent *dent; - DIR *dirp; - int len; + long fd, maxfd; - /* Check for a /proc/$$/fd directory. */ - len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid()); - if (len > 0 && (size_t)len <= sizeof(fdpath) && (dirp = opendir(fdpath))) { - while ((dent = readdir(dirp)) != NULL) { - fd = strtol(dent->d_name, &endp, 10); - if (dent->d_name != endp && *endp == '\0' && - fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp)) - (void) close((int) fd); - } - (void) closedir(dirp); - } else -#endif - { /* * Fall back on sysconf() or getdtablesize(). We avoid checking * resource limits since it is possible to open a file descriptor @@ -99,11 +78,78 @@ closefrom(int lowfd) maxfd = getdtablesize(); #endif /* HAVE_SYSCONF */ if (maxfd < 0) - maxfd = OPEN_MAX; + maxfd = OPEN_MAX; for (fd = lowfd; fd < maxfd; fd++) - (void) close((int) fd); + (void) close((int) fd); +} +#endif /* HAVE_FCNTL_CLOSEM */ + +#ifdef HAVE_FCNTL_CLOSEM +void +closefrom(int lowfd) +{ + (void) fcntl(lowfd, F_CLOSEM, 0); +} +#elif defined(HAVE_LIBPROC_H) && defined(HAVE_PROC_PIDINFO) +void +closefrom(int lowfd) +{ + int i, r, sz; + pid_t pid = getpid(); + struct proc_fdinfo *fdinfo_buf = NULL; + + sz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (sz == 0) + return; /* no fds, really? */ + else if (sz == -1) + goto fallback; + if ((fdinfo_buf = malloc(sz)) == NULL) + goto fallback; + r = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdinfo_buf, sz); + if (r < 0 || r > sz) + goto fallback; + for (i = 0; i < r / (int)PROC_PIDLISTFD_SIZE; i++) { + if (fdinfo_buf[i].proc_fd >= lowfd) + close(fdinfo_buf[i].proc_fd); + } + free(fdinfo_buf); + return; + fallback: + free(fdinfo_buf); + closefrom_fallback(lowfd); + return; +} +#elif defined(HAVE_DIRFD) && defined(HAVE_PROC_PID) +void +closefrom(int lowfd) +{ + long fd; + char fdpath[PATH_MAX], *endp; + struct dirent *dent; + DIR *dirp; + int len; + + /* Check for a /proc/$$/fd directory. */ + len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid()); + if (len > 0 && (size_t)len < sizeof(fdpath) && (dirp = opendir(fdpath))) { + while ((dent = readdir(dirp)) != NULL) { + fd = strtol(dent->d_name, &endp, 10); + if (dent->d_name != endp && *endp == '\0' && + fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp)) + (void) close((int) fd); + } + (void) closedir(dirp); + return; } + /* /proc/$$/fd strategy failed, fall back to brute force closure */ + closefrom_fallback(lowfd); +} +#else +void +closefrom(int lowfd) +{ + closefrom_fallback(lowfd); } #endif /* !HAVE_FCNTL_CLOSEM */ #endif /* HAVE_CLOSEFROM */ diff --git a/configure.ac b/configure.ac index 97010df4..8dd00d79 100644 --- a/configure.ac +++ b/configure.ac @@ -99,6 +99,7 @@ AC_CHECK_HEADERS([ \ dirent.h \ fcntl.h \ inttypes.h \ + libproc.h \ libutil.h \ ndir.h \ paths.h \ @@ -124,7 +125,8 @@ AC_CHECK_FUNCS([ \ dirfd \ flock \ prctl \ - sysconf \ + proc_pidinfo \ + sysconf ]) # Check for functions with a compatibility implementation. From 76cfb5f471ac785b133cdfd6ec53a5097c0bdf3c Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 26 Nov 2020 09:19:10 +0000 Subject: [PATCH 11/90] Add -N flag to display-panes to ignore keys, GitHub issue 2473. --- cmd-display-panes.c | 15 +++++++++++---- tmux.1 | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 352b2e4d..64efb89a 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_display_panes_entry = { .name = "display-panes", .alias = "displayp", - .args = { "bd:t:", 0, 1 }, - .usage = "[-b] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]", + .args = { "bd:Nt:", 0, 1 }, + .usage = "[-bN] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]", .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, .exec = cmd_display_panes_exec @@ -284,8 +284,15 @@ cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item) else cdata->item = item; - server_client_set_overlay(tc, delay, NULL, NULL, cmd_display_panes_draw, - cmd_display_panes_key, cmd_display_panes_free, cdata); + if (args_has(args, 'N')) { + server_client_set_overlay(tc, delay, NULL, NULL, + cmd_display_panes_draw, NULL, cmd_display_panes_free, + cdata); + } else { + server_client_set_overlay(tc, delay, NULL, NULL, + cmd_display_panes_draw, cmd_display_panes_key, + cmd_display_panes_free, cdata); + } if (args_has(args, 'b')) return (CMD_RETURN_NORMAL); diff --git a/tmux.1 b/tmux.1 index 37f6f165..4cc4bfe4 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2098,7 +2098,7 @@ starts without the option information. This command works only if at least one client is attached. .It Xo .Ic display-panes -.Op Fl b +.Op Fl bN .Op Fl d Ar duration .Op Fl t Ar target-client .Op Ar template @@ -2111,7 +2111,8 @@ See the and .Ic display-panes-active-colour session options. -The indicator is closed when a key is pressed or +The indicator is closed when a key is pressed (unless +.Fl N is given) or .Ar duration milliseconds have passed. If From fd5c3e6122faea45d3eb44275424d78bc67b0ce2 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 26 Nov 2020 13:06:21 +0000 Subject: [PATCH 12/90] Fix check for vertical centre. --- cmd-display-menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 7115dc81..ae000abe 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -155,7 +155,7 @@ cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, else format_add(ft, "popup_centre_x", "%ld", n); n = (tty->sy - 1) / 2 + h / 2; - if (n + h >= tty->sy) + if (n >= tty->sy) format_add(ft, "popup_centre_y", "%u", tty->sy - h); else format_add(ft, "popup_centre_y", "%ld", n); From 33046ecee2090a7ff733097d1c05d377936b3e5f Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 30 Nov 2020 13:37:45 +0000 Subject: [PATCH 13/90] Ignore running command when checking for no-hooks flag if it is blocked. GitHub issue 2483. --- cmd-queue.c | 6 +++++- tmux.1 | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd-queue.c b/cmd-queue.c index 36f1c9be..05f439f5 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -768,7 +768,11 @@ cmdq_running(struct client *c) { struct cmdq_list *queue = cmdq_get(c); - return (queue->item); + if (queue->item == NULL) + return (NULL); + if (queue->item->flags & CMDQ_WAITING) + return (NULL); + return (queue->item); } /* Print a guard line. */ diff --git a/tmux.1 b/tmux.1 index 4cc4bfe4..b3a74b0a 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2112,7 +2112,8 @@ and .Ic display-panes-active-colour session options. The indicator is closed when a key is pressed (unless -.Fl N is given) or +.Fl N +is given) or .Ar duration milliseconds have passed. If From 9a74bba007a60b93d1fdf68772e5cfb61b3558ff Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 1 Dec 2020 08:12:58 +0000 Subject: [PATCH 14/90] Make replacement of ##s consistent when drawing formats, whether followed by [ or not. Add a flag (e) to the q: format modifier to double up #s and use it for the window_flags format variable so it doesn't end up escaping any following text. GitHub issue 2485. --- format-draw.c | 8 ++++++-- format.c | 32 +++++++++++++++++++++++++++++--- options-table.c | 4 ++-- tmux.1 | 7 ++++++- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/format-draw.c b/format-draw.c index e73c5df4..67b961d9 100644 --- a/format-draw.c +++ b/format-draw.c @@ -563,13 +563,17 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, if (cp[0] == '#' && cp[1] != '[' && cp[1] != '\0') { for (n = 1; cp[n] == '#'; n++) /* nothing */; + even = ((n % 2) == 0); if (cp[n] != '[') { - width[current] += n; cp += n; + if (even) + n = (n / 2); + else + n = (n / 2) + 1; + width[current] += n; format_draw_many(&ctx[current], &sy, '#', n); continue; } - even = ((n % 2) == 0); if (even) cp += (n + 1); else diff --git a/format.c b/format.c index 91955259..98dc4cb7 100644 --- a/format.c +++ b/format.c @@ -99,6 +99,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_PRETTY 0x400 #define FORMAT_LENGTH 0x800 #define FORMAT_WIDTH 0x1000 +#define FORMAT_ESCAPE 0x2000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 10 @@ -1394,6 +1395,23 @@ format_quote(const char *s) return (out); } +/* Escape #s in string. */ +static char * +format_escape(const char *s) +{ + const char *cp; + char *out, *at; + + at = out = xmalloc(strlen(s) * 2 + 1); + for (cp = s; *cp != '\0'; cp++) { + if (*cp == '#') + *at++ = '#'; + *at++ = *cp; + } + *at = '\0'; + return (out); +} + /* Make a prettier time. */ static char * format_pretty_time(time_t t) @@ -1539,6 +1557,11 @@ found: found = xstrdup(format_quote(saved)); free(saved); } + if (modifiers & FORMAT_ESCAPE) { + saved = found; + found = xstrdup(format_escape(saved)); + free(saved); + } return (found); } @@ -1689,7 +1712,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, cp++; /* Check single character modifiers with no arguments. */ - if (strchr("lbdnqwETSWP<>", cp[0]) != NULL && + if (strchr("lbdnwETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -1710,7 +1733,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, } /* Now try single character with arguments. */ - if (strchr("mCst=pe", cp[0]) == NULL) + if (strchr("mCst=peq", cp[0]) == NULL) break; c = cp[0]; @@ -2216,7 +2239,10 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, time_format = format_strip(fm->argv[1]); break; case 'q': - modifiers |= FORMAT_QUOTE; + if (fm->argc < 1) + modifiers |= FORMAT_QUOTE; + else if (strchr(fm->argv[0], 'e') != NULL) + modifiers |= FORMAT_ESCAPE; break; case 'E': modifiers |= FORMAT_EXPAND; diff --git a/options-table.c b/options-table.c index 2ae49d7a..56e1c895 100644 --- a/options-table.c +++ b/options-table.c @@ -1014,7 +1014,7 @@ const struct options_table_entry options_table[] = { { .name = "window-status-current-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .default_str = "#I:#W#{?window_flags,#{q/e:window_flags}, }", .text = "Format of the current window in the status line." }, @@ -1030,7 +1030,7 @@ const struct options_table_entry options_table[] = { { .name = "window-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .default_str = "#I:#W#{?window_flags,#{q/e:window_flags}, }", .text = "Format of windows in the status line, except the current " "window." }, diff --git a/tmux.1 b/tmux.1 index b3a74b0a..9d1021f3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4638,7 +4638,12 @@ of the variable respectively. .Ql q:\& will escape .Xr sh 1 -special characters. +special characters or with an +.Ql e +suffix, escape hash characters (so +.Ql # +becomes +.Ql ## ). .Ql E:\& will expand the format twice, for example .Ql #{E:status-left} From f0c1233d4f97b499dd51688b089ad7c485c14b2a Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 1 Dec 2020 10:48:03 +0000 Subject: [PATCH 15/90] Leave newlines inside multiline quotes. --- cmd-parse.y | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd-parse.y b/cmd-parse.y index c8995d8b..7b42c621 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -1505,8 +1505,12 @@ yylex_token(int ch) state == NONE) break; - /* Spaces and comments inside quotes after \n are removed. */ + /* + * Spaces and comments inside quotes after \n are removed but + * the \n is left. + */ if (ch == '\n' && state != NONE) { + yylex_append1(&buf, &len, '\n'); while ((ch = yylex_getc()) == ' ' || ch == '\t') /* nothing */; if (ch != '#') From fd451aa7962f399250fd166f207451fcf4b9cb94 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 3 Dec 2020 07:12:11 +0000 Subject: [PATCH 16/90] Redraw any visible modes when status line changes so that formats like the pane title are updated. GitHub issue 2487. Also a man page fix from jmc. --- server-client.c | 24 ++++++++++++++++++++++++ tmux.1 | 2 +- tmux.h | 1 + tty.c | 4 ++-- window-buffer.c | 12 ++++++++++++ window-client.c | 12 ++++++++++++ window-tree.c | 12 ++++++++++++ 7 files changed, 64 insertions(+), 3 deletions(-) diff --git a/server-client.c b/server-client.c index 3e256a92..f85304a6 100644 --- a/server-client.c +++ b/server-client.c @@ -42,6 +42,7 @@ static void server_client_repeat_timer(int, short, void *); static void server_client_click_timer(int, short, void *); static void server_client_check_exit(struct client *); static void server_client_check_redraw(struct client *); +static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); static void server_client_reset_state(struct client *); static int server_client_assume_paste(struct session *); @@ -1355,6 +1356,7 @@ server_client_loop(void) TAILQ_FOREACH(c, &clients, entry) { server_client_check_exit(c); if (c->session != NULL) { + server_client_check_modes(c); server_client_check_redraw(c); server_client_reset_state(c); } @@ -1810,6 +1812,28 @@ server_client_redraw_timer(__unused int fd, __unused short events, log_debug("redraw timer fired"); } +/* + * Check if modes need to be updated. Only modes in the current window are + * updated and it is done when the status line is redrawn. + */ +static void +server_client_check_modes(struct client *c) +{ + struct window *w = c->session->curw->window; + struct window_pane *wp; + struct window_mode_entry *wme; + + if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) + return; + if (~c->flags & CLIENT_REDRAWSTATUS) + return; + TAILQ_FOREACH(wp, &w->panes, entry) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode->update != NULL) + wme->mode->update(wme); + } +} + /* Check for client redraws. */ static void server_client_check_redraw(struct client *c) diff --git a/tmux.1 b/tmux.1 index 9d1021f3..f9a9cdf3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4643,7 +4643,7 @@ special characters or with an suffix, escape hash characters (so .Ql # becomes -.Ql ## ). +.Ql ## ) . .Ql E:\& will expand the format twice, for example .Ql #{E:status-left} diff --git a/tmux.h b/tmux.h index 1845775d..986eed00 100644 --- a/tmux.h +++ b/tmux.h @@ -887,6 +887,7 @@ struct window_mode { struct cmd_find_state *, struct args *); void (*free)(struct window_mode_entry *); void (*resize)(struct window_mode_entry *, u_int, u_int); + void (*update)(struct window_mode_entry *); void (*key)(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); diff --git a/tty.c b/tty.c index 777b639b..fac7a99e 100644 --- a/tty.c +++ b/tty.c @@ -2447,7 +2447,7 @@ tty_check_fg(struct tty *tty, int *palette, struct grid_cell *gc) /* Is this a 256-colour colour? */ if (gc->fg & COLOUR_FLAG_256) { /* And not a 256 colour mode? */ - if (colours != 256) { + if (colours < 256) { gc->fg = colour_256to16(gc->fg); if (gc->fg & 8) { gc->fg &= 7; @@ -2500,7 +2500,7 @@ tty_check_bg(struct tty *tty, int *palette, struct grid_cell *gc) * palette. Bold background doesn't exist portably, so just * discard the bold bit if set. */ - if (colours != 256) { + if (colours < 256) { gc->bg = colour_256to16(gc->bg); if (gc->bg & 8) { gc->bg &= 7; diff --git a/window-buffer.c b/window-buffer.c index ae6f13ce..4599cbc5 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -31,6 +31,7 @@ static struct screen *window_buffer_init(struct window_mode_entry *, static void window_buffer_free(struct window_mode_entry *); static void window_buffer_resize(struct window_mode_entry *, u_int, u_int); +static void window_buffer_update(struct window_mode_entry *); static void window_buffer_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -63,6 +64,7 @@ const struct window_mode window_buffer_mode = { .init = window_buffer_init, .free = window_buffer_free, .resize = window_buffer_resize, + .update = window_buffer_update, .key = window_buffer_key, }; @@ -335,6 +337,16 @@ window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_buffer_update(struct window_mode_entry *wme) +{ + struct window_buffer_modedata *data = wme->data; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; +} + static void window_buffer_do_delete(void *modedata, void *itemdata, __unused struct client *c, __unused key_code key) diff --git a/window-client.c b/window-client.c index 5e02462b..ec3c646a 100644 --- a/window-client.c +++ b/window-client.c @@ -30,6 +30,7 @@ static struct screen *window_client_init(struct window_mode_entry *, static void window_client_free(struct window_mode_entry *); static void window_client_resize(struct window_mode_entry *, u_int, u_int); +static void window_client_update(struct window_mode_entry *); static void window_client_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -59,6 +60,7 @@ const struct window_mode window_client_mode = { .init = window_client_init, .free = window_client_free, .resize = window_client_resize, + .update = window_client_update, .key = window_client_key, }; @@ -311,6 +313,16 @@ window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_client_update(struct window_mode_entry *wme) +{ + struct window_client_modedata *data = wme->data; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; +} + static void window_client_do_detach(void *modedata, void *itemdata, __unused struct client *c, key_code key) diff --git a/window-tree.c b/window-tree.c index a687af16..1498b337 100644 --- a/window-tree.c +++ b/window-tree.c @@ -29,6 +29,7 @@ static struct screen *window_tree_init(struct window_mode_entry *, static void window_tree_free(struct window_mode_entry *); static void window_tree_resize(struct window_mode_entry *, u_int, u_int); +static void window_tree_update(struct window_mode_entry *); static void window_tree_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -79,6 +80,7 @@ const struct window_mode window_tree_mode = { .init = window_tree_init, .free = window_tree_free, .resize = window_tree_resize, + .update = window_tree_update, .key = window_tree_key, }; @@ -937,6 +939,16 @@ window_tree_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_tree_update(struct window_mode_entry *wme) +{ + struct window_tree_modedata *data = wme->data; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; +} + static char * window_tree_get_target(struct window_tree_itemdata *item, struct cmd_find_state *fs) From ed786309ccc7ceb2077f658e689a5f35549bb2bc Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 7 Dec 2020 09:23:57 +0000 Subject: [PATCH 17/90] Do not clear the wrapped flag on linefeeds if it is already set - this does not appear to be what applications want. GitHub issue 2478 and 2414. --- screen-write.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/screen-write.c b/screen-write.c index ab6a020c..92f1aa39 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1240,8 +1240,6 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) gl = grid_get_line(gd, gd->hsize + s->cy); if (wrapped) gl->flags |= GRID_LINE_WRAPPED; - else - gl->flags &= ~GRID_LINE_WRAPPED; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, s->rupper, s->rlower); From f6095cad993293ec0f1988c1f6ae22921f9d2b2e Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 7 Dec 2020 09:46:58 +0000 Subject: [PATCH 18/90] Do not include the status line size when working out the character for the pane status line. GitHub issue 2493. --- screen-redraw.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index 50df2671..47c1a838 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -407,7 +407,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, struct format_tree *ft; char *expanded; int pane_status = rctx->pane_status; - u_int width, i, cell_type, top, px, py; + u_int width, i, cell_type, px, py; struct screen_write_ctx ctx; struct screen old; @@ -432,16 +432,12 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, screen_write_start(&ctx, &wp->status_screen); - if (rctx->statustop) - top = rctx->statuslines; - else - top = 0; for (i = 0; i < width; i++) { px = wp->xoff + 2 + i; if (rctx->pane_status == PANE_STATUS_TOP) - py = top + wp->yoff - 1; + py = wp->yoff - 1; else - py = top + wp->yoff + wp->sy; + py = wp->yoff + wp->sy; cell_type = screen_redraw_type_of_cell(c, px, py, pane_status); screen_redraw_border_set(wp, pane_lines, cell_type, &gc); screen_write_cell(&ctx, &gc); From 681c0d2bfbf22a51f90c977d0b3e91d30c23c11e Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 7 Dec 2020 12:12:40 +0000 Subject: [PATCH 19/90] Include compat.h after system headers, GitHub issue 2492. --- compat/closefrom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compat/closefrom.c b/compat/closefrom.c index 28be3680..be008239 100644 --- a/compat/closefrom.c +++ b/compat/closefrom.c @@ -14,8 +14,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "compat.h" - #ifndef HAVE_CLOSEFROM #include @@ -50,6 +48,8 @@ # include #endif +#include "compat.h" + #ifndef OPEN_MAX # define OPEN_MAX 256 #endif From 8bd29a30bff4e9d50765e2168a7aad11e163ccde Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 15 Dec 2020 08:31:50 +0000 Subject: [PATCH 20/90] Make synchronize-panes a pane option and add -U flag to set-option to unset an option on all panes. GitHub issue 2491 from Rostislav Nesin. --- cmd-set-option.c | 21 +++++++++++++++++---- format.c | 2 +- options-table.c | 2 +- tmux.1 | 19 ++++++++++++------- window.c | 29 ++++++++++++++++++----------- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/cmd-set-option.c b/cmd-set-option.c index 0df12aa0..70e3c54d 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -33,8 +33,8 @@ const struct cmd_entry cmd_set_option_entry = { .name = "set-option", .alias = "set", - .args = { "aFgopqst:uw", 1, 2 }, - .usage = "[-aFgopqsuw] " CMD_TARGET_PANE_USAGE " option [value]", + .args = { "aFgopqst:uUw", 1, 2 }, + .usage = "[-aFgopqsuUw] " CMD_TARGET_PANE_USAGE " option [value]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -74,8 +74,9 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); int append = args_has(args, 'a'); struct cmd_find_state *target = cmdq_get_target(item); + struct window_pane *loop; struct options *oo; - struct options_entry *parent, *o; + struct options_entry *parent, *o, *po; char *name, *argument, *value = NULL, *cause; int window, idx, already, error, ambiguous; int scope; @@ -148,7 +149,19 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) } /* Change the option. */ - if (args_has(args, 'u')) { + if (args_has(args, 'U') && scope == OPTIONS_TABLE_WINDOW) { + TAILQ_FOREACH(loop, &target->w->panes, entry) { + po = options_get_only(loop->options, name); + if (po == NULL) + continue; + if (options_remove_or_default(po, idx, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + goto fail; + } + } + } + if (args_has(args, 'u') || args_has(args, 'U')) { if (o == NULL) goto out; if (options_remove_or_default(o, idx, &cause) != 0) { diff --git a/format.c b/format.c index 98dc4cb7..3611efe9 100644 --- a/format.c +++ b/format.c @@ -3085,7 +3085,7 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) format_add_cb(ft, "pane_in_mode", format_cb_pane_in_mode); format_add(ft, "pane_synchronized", "%d", - !!options_get_number(w->options, "synchronize-panes")); + !!options_get_number(wp->options, "synchronize-panes")); if (wp->searchstr != NULL) format_add(ft, "pane_search_string", "%s", wp->searchstr); diff --git a/options-table.c b/options-table.c index 56e1c895..34a4c57b 100644 --- a/options-table.c +++ b/options-table.c @@ -958,7 +958,7 @@ const struct options_table_entry options_table[] = { { .name = "synchronize-panes", .type = OPTIONS_TABLE_FLAG, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_num = 0, .text = "Whether typing should be sent to all panes simultaneously." }, diff --git a/tmux.1 b/tmux.1 index f9a9cdf3..344f3b72 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3136,7 +3136,7 @@ abc123 Commands which set options are as follows: .Bl -tag -width Ds .It Xo Ic set-option -.Op Fl aFgopqsuw +.Op Fl aFgopqsuUw .Op Fl t Ar target-pane .Ar option Ar value .Xc @@ -3169,6 +3169,11 @@ flag unsets an option, so a session inherits the option from the global options (or with .Fl g , restores a global option to the default). +.Fl U +unsets an option (like +.Fl u ) +but if the option is a pane option also unsets the option on any panes in the +window. .Ar value depends on the option and may be a number, a string, or a flag (on, off, or omitted to toggle). @@ -4062,12 +4067,6 @@ see the section. Attributes are ignored. .Pp -.It Xo Ic synchronize-panes -.Op Ic on | off -.Xc -Duplicate input to any pane to all other panes in the same window (only -for panes that are not in any special mode). -.Pp .It Ic window-status-activity-style Ar style Set status line style for windows with an activity alert. For how to specify @@ -4190,6 +4189,12 @@ The pane may be reactivated with the .Ic respawn-pane command. .Pp +.It Xo Ic synchronize-panes +.Op Ic on | off +.Xc +Duplicate input to all other panes in the same window where this option is also +on (only for panes that are not in any mode). +.Pp .It Ic window-active-style Ar style Set the pane style when it is the active pane. For how to specify diff --git a/window.c b/window.c index 66298495..22986a04 100644 --- a/window.c +++ b/window.c @@ -1145,12 +1145,27 @@ window_pane_reset_mode_all(struct window_pane *wp) window_pane_reset_mode(wp); } +static void +window_pane_copy_key(struct window_pane *wp, key_code key) +{ + struct window_pane *loop; + + TAILQ_FOREACH(loop, &wp->window->panes, entry) { + if (loop != wp && + TAILQ_EMPTY(&loop->modes) && + loop->fd != -1 && + (~loop->flags & PANE_INPUTOFF) && + window_pane_visible(loop) && + options_get_number(loop->options, "synchronize-panes")) + input_key_pane(loop, key, NULL); + } +} + int window_pane_key(struct window_pane *wp, struct client *c, struct session *s, struct winlink *wl, key_code key, struct mouse_event *m) { struct window_mode_entry *wme; - struct window_pane *wp2; if (KEYC_IS_MOUSE(key) && m == NULL) return (-1); @@ -1172,16 +1187,8 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, if (KEYC_IS_MOUSE(key)) return (0); - if (options_get_number(wp->window->options, "synchronize-panes")) { - TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (wp2 != wp && - TAILQ_EMPTY(&wp2->modes) && - wp2->fd != -1 && - (~wp2->flags & PANE_INPUTOFF) && - window_pane_visible(wp2)) - input_key_pane(wp2, key, NULL); - } - } + if (options_get_number(wp->options, "synchronize-panes")) + window_pane_copy_key(wp, key); return (0); } From c43f2dce1b0ca64b43f7614d4da52bc9f2c195fe Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 22 Dec 2020 09:22:14 +0000 Subject: [PATCH 21/90] Break cursor movement in grid into a common set of functions that can handle line wrapping and so on in one place and use them for the obvious copy mode commands. From Anindya Mukherjee. --- Makefile | 4 +- grid-reader.c | 303 ++++++++++++++++++++++++++++++++++++++++++++++ tmux.h | 23 ++++ window-copy.c | 329 ++++++++++++++++++++++++++++---------------------- 4 files changed, 515 insertions(+), 144 deletions(-) create mode 100644 grid-reader.c diff --git a/Makefile b/Makefile index 7fb664ad..21141317 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,7 @@ SRCS= alerts.c \ file.c \ format.c \ format-draw.c \ + grid-reader.c \ grid-view.c \ grid.c \ input-keys.c \ @@ -123,8 +124,7 @@ SRCS= alerts.c \ window-customize.c \ window-tree.c \ window.c \ - xmalloc.c \ - xterm-keys.c + xmalloc.c CDIAGFLAGS+= -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 CDIAGFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations diff --git a/grid-reader.c b/grid-reader.c new file mode 100644 index 00000000..a1af3aaa --- /dev/null +++ b/grid-reader.c @@ -0,0 +1,303 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 Anindya Mukherjee + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "tmux.h" + +/* Initialise virtual cursor. */ +void +grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy) +{ + gr->gd = gd; + gr->cx = cx; + gr->cy = cy; +} + +/* Get cursor position from reader. */ +void +grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy) +{ + *cx = gr->cx; + *cy = gr->cy; +} + +/* Get length of line containing the cursor. */ +u_int +grid_reader_line_length(struct grid_reader *gr) +{ + return (grid_line_length(gr->gd, gr->cy)); +} + +/* Move cursor forward one position. */ +void +grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all) +{ + u_int px; + struct grid_cell gc; + + if (all) + px = gr->gd->sx; + else + px = grid_reader_line_length(gr); + + if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) { + grid_reader_cursor_start_of_line(gr, 0); + grid_reader_cursor_down(gr); + } else if (gr->cx < px) { + gr->cx++; + while (gr->cx < px) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx++; + } + } +} + +/* Move cursor back one position. */ +void +grid_reader_cursor_left(struct grid_reader *gr) +{ + struct grid_cell gc; + + while (gr->cx > 0) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx--; + } + if (gr->cx == 0 && gr->cy > 0) { + grid_reader_cursor_up(gr); + grid_reader_cursor_end_of_line(gr, 0, 0); + } else if (gr->cx > 0) + gr->cx--; +} + +/* Move cursor down one line. */ +void +grid_reader_cursor_down(struct grid_reader *gr) +{ + struct grid_cell gc; + + if (gr->cy < gr->gd->hsize + gr->gd->sy - 1) + gr->cy++; + while (gr->cx > 0) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx--; + } +} + +/* Move cursor up one line. */ +void +grid_reader_cursor_up(struct grid_reader *gr) +{ + struct grid_cell gc; + + if (gr->cy > 0) + gr->cy--; + while (gr->cx > 0) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx--; + } +} + +/* Move cursor to the start of the line. */ +void +grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap) +{ + if (wrap) { + while (gr->cy > 0 && + grid_get_line(gr->gd, gr->cy - 1)->flags & + GRID_LINE_WRAPPED) + gr->cy--; + } + gr->cx = 0; +} + +/* Move cursor to the end of the line. */ +void +grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all) +{ + u_int yy; + + if (wrap) { + yy = gr->gd->hsize + gr->gd->sy - 1; + while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags & + GRID_LINE_WRAPPED) + gr->cy++; + } + if (all) + gr->cx = gr->gd->sx; + else + gr->cx = grid_reader_line_length(gr); +} + +/* Check if character under cursor is in set. */ +int +grid_reader_in_set(struct grid_reader *gr, const char *set) +{ + struct grid_cell gc; + + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (gc.flags & GRID_FLAG_PADDING) + return (0); + return (utf8_cstrhas(set, &gc.data)); +} + +/* Move cursor to the start of the next word. */ +void +grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) +{ + u_int xx, yy; + int expected = 0; + + /* Do not break up wrapped words. */ + if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) + xx = grid_reader_line_length(gr) - 1; + else + xx = grid_reader_line_length(gr); + yy = gr->gd->hsize + gr->gd->sy - 1; + + /* + * If we started inside a word, skip over word characters. Then skip + * over separators till the next word. + * + * expected is initially set to 0 for the former and then 1 for the + * latter. It is finally set to 0 when the beginning of the next word is + * found. + */ + do { + while (gr->cx > xx || + grid_reader_in_set(gr, separators) == expected) { + /* Move down if we are past the end of the line. */ + if (gr->cx > xx) { + if (gr->cy == yy) + return; + grid_reader_cursor_start_of_line(gr, 0); + grid_reader_cursor_down(gr); + + if (grid_get_line(gr->gd, gr->cy)->flags & + GRID_LINE_WRAPPED) + xx = grid_reader_line_length(gr) - 1; + else + xx = grid_reader_line_length(gr); + } else + gr->cx++; + } + expected = !expected; + } while (expected == 1); +} + +/* Move cursor to the end of the next word. */ +void +grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) +{ + u_int xx, yy; + int expected = 1; + + /* Do not break up wrapped words. */ + if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) + xx = grid_reader_line_length(gr) - 1; + else + xx = grid_reader_line_length(gr); + yy = gr->gd->hsize + gr->gd->sy - 1; + + /* + * If we started on a separator, skip over separators. Then skip over + * word characters till the next separator. + * + * expected is initially set to 1 for the former and then 1 for the + * latter. It is finally set to 1 when the end of the next word is + * found. + */ + do { + while (gr->cx > xx || + grid_reader_in_set(gr, separators) == expected) { + /* Move down if we are past the end of the line. */ + if (gr->cx > xx) { + if (gr->cy == yy) + return; + grid_reader_cursor_start_of_line(gr, 0); + grid_reader_cursor_down(gr); + + if (grid_get_line(gr->gd, gr->cy)->flags & + GRID_LINE_WRAPPED) + xx = grid_reader_line_length(gr) - 1; + else + xx = grid_reader_line_length(gr); + } else + gr->cx++; + } + expected = !expected; + } while (expected == 0); +} + +/* Move to the previous place where a word begins. */ +void +grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, + int already) +{ + int oldx, oldy, r; + + /* Move back to the previous word character. */ + if (already || grid_reader_in_set(gr, separators)) { + for (;;) { + if (gr->cx > 0) { + gr->cx--; + if (!grid_reader_in_set(gr, separators)) + break; + } else { + if (gr->cy == 0) + return; + grid_reader_cursor_up(gr); + grid_reader_cursor_end_of_line(gr, 0, 0); + + /* Stop if separator at EOL. */ + if (gr->cx > 0) { + oldx = gr->cx; + gr->cx--; + r = grid_reader_in_set(gr, separators); + gr->cx = oldx; + if (r) + break; + } + } + } + } + + /* Move back to the beginning of this word. */ + do { + oldx = gr->cx; + oldy = gr->cy; + if (gr->cx == 0) { + if (gr->cy == 0 || + ~grid_get_line(gr->gd, gr->cy - 1)->flags & + GRID_LINE_WRAPPED) + break; + grid_reader_cursor_up(gr); + grid_reader_cursor_end_of_line(gr, 0, 0); + } + if (gr->cx > 0) + gr->cx--; + } while (!grid_reader_in_set(gr, separators)); + gr->cx = oldx; + gr->cy = oldy; +} diff --git a/tmux.h b/tmux.h index 986eed00..0defff34 100644 --- a/tmux.h +++ b/tmux.h @@ -726,6 +726,13 @@ struct grid { struct grid_line *linedata; }; +/* Virtual cursor in a grid. */ +struct grid_reader { + struct grid *gd; + u_int cx; + u_int cy; +}; + /* Style alignment. */ enum style_align { STYLE_ALIGN_DEFAULT, @@ -2548,6 +2555,22 @@ void grid_wrap_position(struct grid *, u_int, u_int, u_int *, u_int *); void grid_unwrap_position(struct grid *, u_int *, u_int *, u_int, u_int); u_int grid_line_length(struct grid *, u_int); +/* grid-reader.c */ +void grid_reader_start(struct grid_reader *, struct grid *, u_int, u_int); +void grid_reader_get_cursor(struct grid_reader *, u_int *, u_int *); +u_int grid_reader_line_length(struct grid_reader *); +int grid_reader_in_set(struct grid_reader *, const char *); +void grid_reader_cursor_right(struct grid_reader *, int, int); +void grid_reader_cursor_left(struct grid_reader *); +void grid_reader_cursor_down(struct grid_reader *); +void grid_reader_cursor_up(struct grid_reader *); +void grid_reader_cursor_start_of_line(struct grid_reader *, int); +void grid_reader_cursor_end_of_line(struct grid_reader *, int, int); +void grid_reader_cursor_next_word(struct grid_reader *, const char *); +void grid_reader_cursor_next_word_end(struct grid_reader *, const char *); +void grid_reader_cursor_previous_word(struct grid_reader *, const char *, + int); + /* grid-view.c */ void grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *); void grid_view_set_cell(struct grid *, u_int, u_int, diff --git a/window-copy.c b/window-copy.c index 6dc03b34..764f6b5b 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3993,18 +3993,31 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid *gd = back_s->grid; - u_int py; + struct grid_reader gr; + u_int px, py, cy, yy, ny, hsize; - if (data->cx == 0 && data->lineflag == LINE_SEL_NONE) { - py = screen_hsize(back_s) + data->cy - data->oy; - while (py > 0 && - grid_get_line(gd, py - 1)->flags & GRID_LINE_WRAPPED) { - window_copy_cursor_up(wme, 0); - py = screen_hsize(back_s) + data->cy - data->oy; - } + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_start_of_line(&gr, 1); + grid_reader_get_cursor(&gr, &px, &py); + + /* Scroll up if we went off the visible screen. */ + yy = hsize - data->oy; + if (py < yy) { + ny = yy - py; + cy = 0; + } else { + ny = 0; + cy = py - yy; } - window_copy_update_cursor(wme, 0, data->cy); + while (ny > 0) { + window_copy_cursor_up(wme, 1); + ny--; + } + window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) window_copy_redraw_lines(wme, data->cy, 1); } @@ -4037,30 +4050,35 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid *gd = back_s->grid; - struct grid_line *gl; - u_int px, py; + struct grid_reader gr; + u_int px, py, cy, yy, ny, hsize; - py = screen_hsize(back_s) + data->cy - data->oy; - px = window_copy_find_length(wme, py); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; - if (data->cx == px && data->lineflag == LINE_SEL_NONE) { - if (data->screen.sel != NULL && data->rectflag) - px = screen_size_x(back_s); - gl = grid_get_line(gd, py); - if (gl->flags & GRID_LINE_WRAPPED) { - while (py < gd->sy + gd->hsize) { - gl = grid_get_line(gd, py); - if (~gl->flags & GRID_LINE_WRAPPED) - break; - window_copy_cursor_down(wme, 0); - py = screen_hsize(back_s) + data->cy - data->oy; - } - px = window_copy_find_length(wme, py); - } + grid_reader_start(&gr, back_s->grid, px, py); + if (data->screen.sel != NULL && data->rectflag) + grid_reader_cursor_end_of_line(&gr, 1, 1); + else + grid_reader_cursor_end_of_line(&gr, 1, 0); + grid_reader_get_cursor(&gr, &px, &py); + + /* Scroll down if we went off the visible screen. */ + cy = py - hsize + data->oy; + yy = screen_size_y(back_s) - 1; + if (cy > yy) + ny = cy - yy; + else + ny = 0; + while (ny > 0) { + window_copy_cursor_down(wme, 1); + ny--; } - window_copy_update_cursor(wme, px, data->cy); - + if (cy > yy) + window_copy_update_cursor(wme, px, yy); + else + window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) window_copy_redraw_lines(wme, data->cy, 1); } @@ -4120,57 +4138,69 @@ static void window_copy_cursor_left(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; - u_int py, cx; - struct grid_cell gc; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, cy, yy, ny, hsize; - py = screen_hsize(data->backing) + data->cy - data->oy; - cx = data->cx; - while (cx > 0) { - grid_get_cell(data->backing->grid, cx, py, &gc); - if (~gc.flags & GRID_FLAG_PADDING) - break; - cx--; + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_left(&gr); + grid_reader_get_cursor(&gr, &px, &py); + + /* Scroll up if we went off the visible screen. */ + yy = hsize - data->oy; + if (py < yy) { + ny = yy - py; + cy = 0; + } else { + ny = 0; + cy = py - yy; } - if (cx == 0 && py > 0) { - window_copy_cursor_up(wme, 0); - window_copy_cursor_end_of_line(wme); - } else if (cx > 0) { - window_copy_update_cursor(wme, cx - 1, data->cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + while (ny > 0) { + window_copy_cursor_up(wme, 1); + ny--; } + window_copy_update_cursor(wme, px, cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); } static void window_copy_cursor_right(struct window_mode_entry *wme, int all) { struct window_copy_mode_data *data = wme->data; - u_int px, py, yy, cx, cy; - struct grid_cell gc; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, cy, yy, ny, hsize; - py = screen_hsize(data->backing) + data->cy - data->oy; - yy = screen_hsize(data->backing) + screen_size_y(data->backing) - 1; - if (all || (data->screen.sel != NULL && data->rectflag)) - px = screen_size_x(&data->screen); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_right(&gr, 1, all); + grid_reader_get_cursor(&gr, &px, &py); + + /* Scroll down if we went off the visible screen. */ + cy = py - hsize + data->oy; + yy = screen_size_y(back_s) - 1; + if (cy > yy) + ny = cy - yy; else - px = window_copy_find_length(wme, py); - - if (data->cx >= px && py < yy) { - window_copy_cursor_start_of_line(wme); - window_copy_cursor_down(wme, 0); - } else if (data->cx < px) { - cx = data->cx + 1; - cy = screen_hsize(data->backing) + data->cy - data->oy; - while (cx < px) { - grid_get_cell(data->backing->grid, cx, cy, &gc); - if (~gc.flags & GRID_FLAG_PADDING) - break; - cx++; - } - window_copy_update_cursor(wme, cx, data->cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + ny = 0; + while (ny > 0) { + window_copy_cursor_down(wme, 1); + ny--; } + if (cy > yy) + window_copy_update_cursor(wme, px, yy); + else + window_copy_update_cursor(wme, px, cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); } static void @@ -4214,13 +4244,30 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wme); + { + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } if (data->lineflag == LINE_SEL_LEFT_RIGHT) - window_copy_cursor_end_of_line(wme); + { + py = screen_hsize(data->backing) + data->cy - data->oy; + if (data->rectflag) + px = screen_size_x(data->backing); + else + px = window_copy_find_length(wme, py); + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } else if (data->lineflag == LINE_SEL_RIGHT_LEFT) - window_copy_cursor_start_of_line(wme); + { + window_copy_update_cursor(wme, 0, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } static void @@ -4256,13 +4303,30 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wme); + { + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } if (data->lineflag == LINE_SEL_LEFT_RIGHT) - window_copy_cursor_end_of_line(wme); + { + py = screen_hsize(data->backing) + data->cy - data->oy; + if (data->rectflag) + px = screen_size_x(data->backing); + else + px = window_copy_find_length(wme, py); + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } else if (data->lineflag == LINE_SEL_RIGHT_LEFT) - window_copy_cursor_start_of_line(wme); + { + window_copy_update_cursor(wme, 0, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } static void @@ -4476,46 +4540,38 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct options *oo = wp->window->options; struct screen *back_s = data->backing; - u_int px, py, xx, yy; - int keys, expected = 1; + struct grid_reader gr; + u_int px, py, cy, yy, ny, hsize; + int keys; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - yy = screen_hsize(back_s) + screen_size_y(back_s) - 1; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + grid_reader_start(&gr, back_s->grid, px, py); keys = options_get_number(oo, "mode-keys"); - if (keys == MODEKEY_VI && !window_copy_in_set(wme, px, py, separators)) - px++; + if (keys == MODEKEY_VI && !grid_reader_in_set(&gr, separators)) + grid_reader_cursor_right(&gr, 0, 0); + grid_reader_cursor_next_word_end(&gr, separators); + if (keys == MODEKEY_VI) + grid_reader_cursor_left(&gr); + grid_reader_get_cursor(&gr, &px, &py); - /* - * First skip past any word characters, then any nonword characters. - * - * expected is initially set to 1 for the former and then 0 for the - * latter. - */ - do { - while (px > xx || - window_copy_in_set(wme, px, py, separators) == expected) { - /* Move down if we're past the end of the line. */ - if (px > xx) { - if (py == yy) - return; - window_copy_cursor_down(wme, 0); - px = 0; - - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - } else - px++; - } - expected = !expected; - } while (expected == 0); - - if (keys == MODEKEY_VI && px != 0) - px--; - - window_copy_update_cursor(wme, px, data->cy); + /* Scroll down if we went off the visible screen. */ + cy = py - hsize + data->oy; + yy = screen_size_y(back_s) - 1; + if (cy > yy) + ny = cy - yy; + else + ny = 0; + while (ny > 0) { + window_copy_cursor_down(wme, 1); + ny--; + } + if (cy > yy) + window_copy_update_cursor(wme, px, yy); + else + window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, no_reset)) window_copy_redraw_lines(wme, data->cy, 1); } @@ -4572,43 +4628,32 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, const char *separators, int already) { struct window_copy_mode_data *data = wme->data; - u_int px, py, hsize; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, cy, yy, ny, hsize; - hsize = screen_hsize(data->backing); px = data->cx; + hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; - /* Move back to the previous word character. */ - if (already || window_copy_in_set(wme, px, py, separators)) { - for (;;) { - if (px > 0) { - px--; - if (!window_copy_in_set(wme, px, py, - separators)) - break; - } else { - if (data->cy == 0 && - (hsize == 0 || data->oy > hsize - 1)) - goto out; - window_copy_cursor_up(wme, 0); + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_previous_word(&gr, separators, already); + grid_reader_get_cursor(&gr, &px, &py); - py = hsize + data->cy - data->oy; - px = window_copy_find_length(wme, py); - - /* Stop if separator at EOL. */ - if (px > 0 && window_copy_in_set(wme, px - 1, - py, separators)) - break; - } - } + /* Scroll up if we went off the visible screen. */ + yy = hsize - data->oy; + if (py < yy) { + ny = yy - py; + cy = 0; + } else { + ny = 0; + cy = py - yy; } - - /* Move back to the beginning of this word. */ - while (px > 0 && !window_copy_in_set(wme, px - 1, py, separators)) - px--; - -out: - window_copy_update_cursor(wme, px, data->cy); + while (ny > 0) { + window_copy_cursor_up(wme, 1); + ny--; + } + window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) window_copy_redraw_lines(wme, data->cy, 1); } From d936fde7ef62863fbb8b142c3ee229cb460dbfdb Mon Sep 17 00:00:00 2001 From: Thomas Adam Date: Thu, 24 Dec 2020 22:22:10 +0000 Subject: [PATCH 22/90] Makefile.am: add grid-reader.c Add grid-reader.c to Makefile.am so it's included for compilation. --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index b40e0944..e9b1cb15 100644 --- a/Makefile.am +++ b/Makefile.am @@ -141,6 +141,7 @@ dist_tmux_SOURCES = \ file.c \ format.c \ format-draw.c \ + grid-reader.c \ grid-view.c \ grid.c \ input-keys.c \ From c68baaad98807963da954bc1aa77c22f2df05ff0 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 28 Dec 2020 09:36:26 +0000 Subject: [PATCH 23/90] Remove current match indicator which can't work anymore since we only search the visible region. From Anindya Mukherjee, GitHub issue 2508. --- window-copy.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/window-copy.c b/window-copy.c index 764f6b5b..d6af397f 100644 --- a/window-copy.c +++ b/window-copy.c @@ -272,7 +272,7 @@ struct window_copy_mode_data { u_char *searchmark; int searchcount; int searchmore; - int searchthis; + int searchall; int searchx; int searchy; int searcho; @@ -396,6 +396,7 @@ window_copy_common_init(struct window_mode_entry *wme) data->searchstr = NULL; } data->searchx = data->searchy = data->searcho = -1; + data->searchall = 1; data->jumptype = WINDOW_COPY_OFF; data->jumpchar = '\0'; @@ -2334,9 +2335,6 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, if (clear != WINDOW_COPY_CMD_CLEAR_NEVER) { window_copy_clear_marks(wme); data->searchx = data->searchy = -1; - } else if (data->searchthis != -1) { - data->searchthis = -1; - action = WINDOW_COPY_CMD_REDRAW; } if (action == WINDOW_COPY_CMD_NOTHING) action = WINDOW_COPY_CMD_REDRAW; @@ -2929,9 +2927,11 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex, if (data->timeout) return (0); - if (wp->searchstr == NULL || wp->searchregex != regex) + if (data->searchall || wp->searchstr == NULL || + wp->searchregex != regex) { visible_only = 0; - else + data->searchall = 0; + } else visible_only = (strcmp(wp->searchstr, str) == 0); free(wp->searchstr); wp->searchstr = xstrdup(str); @@ -3116,7 +3116,6 @@ again: if (!visible_only) { if (stopped) { - data->searchthis = -1; if (nfound > 1000) data->searchcount = 1000; else if (nfound > 100) @@ -3127,10 +3126,6 @@ again: data->searchcount = -1; data->searchmore = 1; } else { - if (which != -1) - data->searchthis = 1 + nfound - which; - else - data->searchthis = -1; data->searchcount = nfound; data->searchmore = 0; } @@ -3366,15 +3361,11 @@ window_copy_write_line(struct window_mode_entry *wme, if (data->searchcount == -1) { size = xsnprintf(hdr, sizeof hdr, "[%u/%u]", data->oy, hsize); - } else if (data->searchthis == -1) { + } else { size = xsnprintf(hdr, sizeof hdr, "(%d%s results) [%u/%u]", data->searchcount, data->searchmore ? "+" : "", data->oy, hsize); - } else { - size = xsnprintf(hdr, sizeof hdr, - "(%d/%d results) [%u/%u]", data->searchthis, - data->searchcount, data->oy, hsize); } } if (size > screen_size_x(s)) From a98ee00dd988b813b0b0cba1af939978505936b7 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 28 Dec 2020 09:40:27 +0000 Subject: [PATCH 24/90] Do not list user options with show-hooks. --- cmd-show-options.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd-show-options.c b/cmd-show-options.c index 8e70eaa9..a9c5bd2a 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -201,11 +201,13 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, u_int idx; int parent; - o = options_first(oo); - while (o != NULL) { - if (options_table_entry(o) == NULL) - cmd_show_options_print(self, item, o, -1, 0); - o = options_next(o); + if (cmd_get_entry(self) != &cmd_show_hooks_entry) { + o = options_first(oo); + while (o != NULL) { + if (options_table_entry(o) == NULL) + cmd_show_options_print(self, item, o, -1, 0); + o = options_next(o); + } } for (oe = options_table; oe->name != NULL; oe++) { if (~oe->scope & scope) From f97305af317daded5d234ea1d3fcea78974f1200 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 30 Dec 2020 18:29:40 +0000 Subject: [PATCH 25/90] Use right format for session loop, GitHub issue 2519. --- format.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/format.c b/format.c index 3611efe9..2d7402af 100644 --- a/format.c +++ b/format.c @@ -1876,7 +1876,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt) RB_FOREACH(s, sessions, &sessions) { format_log(es, "session loop: $%u", s->id); nft = format_create(c, item, FORMAT_NONE, ft->flags); - format_defaults(next.ft, ft->c, s, NULL, NULL); + format_defaults(nft, ft->c, s, NULL, NULL); format_copy_state(&next, es, 0); next.ft = nft; expanded = format_expand1(&next, fmt); From 606bd5f8c6be326ea2e4746de5d5e007fd04eef8 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 1 Jan 2021 08:36:51 +0000 Subject: [PATCH 26/90] Add a -C flag to run-shell to use a tmux command rather than a shell command. --- cmd-run-shell.c | 85 +++++++++++++++++++++++++++++++++++++------------ tmux.1 | 23 ++++++++----- 2 files changed, 80 insertions(+), 28 deletions(-) diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 647f533f..b259276d 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -40,8 +40,8 @@ const struct cmd_entry cmd_run_shell_entry = { .name = "run-shell", .alias = "run", - .args = { "bd:t:", 0, 1 }, - .usage = "[-b] [-d delay] " CMD_TARGET_PANE_USAGE " [shell-command]", + .args = { "bd:Ct:", 0, 1 }, + .usage = "[-bC] [-d delay] " CMD_TARGET_PANE_USAGE " [shell-command]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -50,13 +50,16 @@ const struct cmd_entry cmd_run_shell_entry = { }; struct cmd_run_shell_data { + struct client *client; char *cmd; + int shell; char *cwd; struct cmdq_item *item; struct session *s; int wp_id; struct event timer; int flags; + struct cmd_parse_input pi; }; static void @@ -93,49 +96,69 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); struct cmd_run_shell_data *cdata; + struct client *tc = cmdq_get_target_client(item); struct session *s = target->s; struct window_pane *wp = target->wp; const char *delay; double d; struct timeval tv; char *end; + int wait = !args_has(args, 'b'); + + if ((delay = args_get(args, 'd')) != NULL) { + d = strtod(delay, &end); + if (*end != '\0') { + cmdq_error(item, "invalid delay time: %s", delay); + return (CMD_RETURN_ERROR); + } + } else if (args->argc == 0) + return (CMD_RETURN_NORMAL); cdata = xcalloc(1, sizeof *cdata); if (args->argc != 0) cdata->cmd = format_single_from_target(item, args->argv[0]); + cdata->shell = !args_has(args, 'C'); + if (!cdata->shell) { + memset(&cdata->pi, 0, sizeof cdata->pi); + cmd_get_source(self, &cdata->pi.file, &cdata->pi.line); + if (wait) + cdata->pi.item = item; + cdata->pi.c = tc; + cmd_find_copy_state(&cdata->pi.fs, target); + } + if (args_has(args, 't') && wp != NULL) cdata->wp_id = wp->id; else cdata->wp_id = -1; - if (!args_has(args, 'b')) + if (wait) { + cdata->client = cmdq_get_client(item); cdata->item = item; - else + } else { + cdata->client = tc; cdata->flags |= JOB_NOWAIT; + } + if (cdata->client != NULL) + cdata->client->references++; cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s)); + cdata->s = s; if (s != NULL) session_add_ref(s, __func__); evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata); - - if ((delay = args_get(args, 'd')) != NULL) { - d = strtod(delay, &end); - if (*end != '\0') { - cmdq_error(item, "invalid delay time: %s", delay); - cmd_run_shell_free(cdata); - return (CMD_RETURN_ERROR); - } + if (delay != NULL) { timerclear(&tv); tv.tv_sec = (time_t)d; tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U; evtimer_add(&cdata->timer, &tv); } else - cmd_run_shell_timer(-1, 0, cdata); + event_active(&cdata->timer, EV_TIMEOUT, 1); - if (args_has(args, 'b')) + if (!wait) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } @@ -144,17 +167,37 @@ static void cmd_run_shell_timer(__unused int fd, __unused short events, void* arg) { struct cmd_run_shell_data *cdata = arg; + struct client *c = cdata->client; + const char *cmd = cdata->cmd; + char *error; + struct cmdq_item *item = cdata->item; + enum cmd_parse_status status; - if (cdata->cmd != NULL) { - if (job_run(cdata->cmd, cdata->s, cdata->cwd, NULL, + if (cmd != NULL && cdata->shell) { + if (job_run(cmd, cdata->s, cdata->cwd, NULL, cmd_run_shell_callback, cmd_run_shell_free, cdata, cdata->flags, -1, -1) == NULL) cmd_run_shell_free(cdata); - } else { - if (cdata->item != NULL) - cmdq_continue(cdata->item); - cmd_run_shell_free(cdata); + return; } + + if (cmd != NULL) { + if (item != NULL) { + status = cmd_parse_and_insert(cmd, &cdata->pi, item, + cmdq_get_state(item), &error); + } else { + status = cmd_parse_and_append(cmd, &cdata->pi, c, NULL, + &error); + } + if (status == CMD_PARSE_ERROR) { + cmdq_error(cdata->item, "%s", error); + free(error); + } + } + + if (cdata->item != NULL) + cmdq_continue(cdata->item); + cmd_run_shell_free(cdata); } static void @@ -215,6 +258,8 @@ cmd_run_shell_free(void *data) evtimer_del(&cdata->timer); if (cdata->s != NULL) session_remove_ref(cdata->s, __func__); + if (cdata->client != NULL) + server_client_unref(cdata->client); free(cdata->cwd); free(cdata->cmd); free(cdata); diff --git a/tmux.1 b/tmux.1 index 344f3b72..1376df95 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5827,7 +5827,7 @@ Lock each client individually by running the command specified by the .Ic lock-command option. .It Xo Ic run-shell -.Op Fl b +.Op Fl bC .Op Fl d Ar delay .Op Fl t Ar target-pane .Op Ar shell-command @@ -5835,9 +5835,14 @@ option. .D1 (alias: Ic run ) Execute .Ar shell-command -in the background without creating a window. -Before being executed, shell-command is expanded using the rules specified in -the +or (with +.Fl C ) +a +.Nm +command in the background without creating a window. +Before being executed, +.Ar shell-command +is expanded using the rules specified in the .Sx FORMATS section. With @@ -5847,11 +5852,13 @@ the command is run in the background. waits for .Ar delay seconds before starting the command. -After the command finishes, any output to stdout is displayed in view mode (in -the pane specified by +If +.Fl C +is not given, any output to stdout is displayed in view mode (in the pane +specified by .Fl t -or the current pane if omitted). -If the command doesn't return success, the exit status is also displayed. +or the current pane if omitted) after the command finishes. +If the command fails, the exit status is also displayed. .It Xo Ic wait-for .Op Fl L | S | U .Ar channel From bd0fb22f0a206a73ec3f41322571d56d9d7d7c5d Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 4 Jan 2021 08:43:16 +0000 Subject: [PATCH 27/90] Add a variant of remain-on-exit that only keeps the pane if the program failed, GitHub issue 2513. --- options-table.c | 8 ++++++-- server-fn.c | 16 ++++++++++++---- tmux.1 | 5 ++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/options-table.c b/options-table.c index 34a4c57b..a8276c86 100644 --- a/options-table.c +++ b/options-table.c @@ -69,6 +69,9 @@ static const char *options_table_set_clipboard_list[] = { static const char *options_table_window_size_list[] = { "largest", "smallest", "manual", "latest", NULL }; +static const char *options_table_remain_on_exit_list[] = { + "off", "on", "failed", NULL +}; /* Status line format. */ #define OPTIONS_TABLE_STATUS_FORMAT1 \ @@ -949,11 +952,12 @@ const struct options_table_entry options_table[] = { }, { .name = "remain-on-exit", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .choices = options_table_remain_on_exit_list, .default_num = 0, .text = "Whether panes should remain ('on') or be automatically " - "killed ('off') when the program inside exits." + "killed ('off' or 'failed') when the program inside exits." }, { .name = "synchronize-panes", diff --git a/server-fn.c b/server-fn.c index d2465569..d0e06c0f 100644 --- a/server-fn.c +++ b/server-fn.c @@ -314,6 +314,7 @@ server_destroy_pane(struct window_pane *wp, int notify) struct grid_cell gc; time_t t; char tim[26]; + int remain_on_exit; if (wp->fd != -1) { bufferevent_free(wp->event); @@ -322,10 +323,17 @@ server_destroy_pane(struct window_pane *wp, int notify) wp->fd = -1; } - if (options_get_number(wp->options, "remain-on-exit")) { - if (~wp->flags & PANE_STATUSREADY) - return; - + remain_on_exit = options_get_number(wp->options, "remain-on-exit"); + if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY)) + return; + switch (remain_on_exit) { + case 0: + break; + case 2: + if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) + break; + /* FALLTHROUGH */ + case 1: if (wp->flags & PANE_STATUSDRAWN) return; wp->flags |= PANE_STATUSDRAWN; diff --git a/tmux.1 b/tmux.1 index 1376df95..941305e3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4181,10 +4181,13 @@ interactive application starts and restores it on exit, so that any output visible before the application starts reappears unchanged after it exits. .Pp .It Xo Ic remain-on-exit -.Op Ic on | off +.Op Ic on | off | failed .Xc A pane with this flag set is not destroyed when the program running in it exits. +If set to +.Ic failed , +then only when the program exit status is not zero. The pane may be reactivated with the .Ic respawn-pane command. From ccb8b9eb2a32fef1c28d45147ccc8a3ff10e656c Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 6 Jan 2021 07:29:49 +0000 Subject: [PATCH 28/90] Remove unused variable, from Ben Boeckel. --- window-copy.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/window-copy.c b/window-copy.c index d6af397f..4c81cb1f 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3009,7 +3009,7 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, struct screen *s = data->backing, ss; struct screen_write_ctx ctx; struct grid *gd = s->grid; - int found, cis, which = -1, stopped = 0; + int found, cis, stopped = 0; int cflags = REG_EXTENDED; u_int px, py, i, b, nfound = 0, width; u_int ssize = 1, start, end; @@ -3072,11 +3072,7 @@ again: if (!found) break; } - nfound++; - if (px == data->cx && - py == gd->hsize + data->cy - data->oy) - which = nfound; if (window_copy_search_mark_at(data, px, py, &b) == 0) { if (b + width > gd->sx * gd->sy) @@ -3088,7 +3084,6 @@ again: else data->searchgen++; } - px += width; } From 199689954b310a1915f573156f94cb93457c71f9 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 6 Jan 2021 07:32:23 +0000 Subject: [PATCH 29/90] Insert joined pane before the target pane with -b, like for split. From Takeshi Banse. --- cmd-join-pane.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 904d2c29..fc95a6b8 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -143,7 +143,10 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); src_wp->flags |= PANE_STYLECHANGED; - TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); + if (flags & SPAWN_BEFORE) + TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry); + else + TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); layout_assign_pane(lc, src_wp); recalculate_sizes(); From b96c5e3687d36cea7b575a7e151326f1b82824d2 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 8 Jan 2021 08:22:10 +0000 Subject: [PATCH 30/90] With incremental search, start empty and only repeat the previous search if the user tries to search again with an empty prompt. This matches emacs behaviour more closely. --- status.c | 46 +++++++++++++++++++++++++++++++--------------- window-copy.c | 4 ++++ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/status.c b/status.c index 668c0a3b..82107c58 100644 --- a/status.c +++ b/status.c @@ -543,7 +543,7 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, prompt_free_cb freecb, void *data, int flags) { struct format_tree *ft; - char *tmp, *cp; + char *tmp; if (fs != NULL) ft = format_create_from_state(NULL, c, fs); @@ -563,7 +563,13 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, c->prompt_string = format_expand_time(ft, msg); - c->prompt_buffer = utf8_fromcstr(tmp); + if (flags & PROMPT_INCREMENTAL) { + c->prompt_last = xstrdup(tmp); + c->prompt_buffer = utf8_fromcstr(""); + } else { + c->prompt_last = NULL; + c->prompt_buffer = utf8_fromcstr(tmp); + } c->prompt_index = utf8_strlen(c->prompt_buffer); c->prompt_inputcb = inputcb; @@ -579,11 +585,8 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs, c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_REDRAWSTATUS; - if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { - xasprintf(&cp, "=%s", tmp); - c->prompt_inputcb(c, c->prompt_data, cp, 0); - free(cp); - } + if (flags & PROMPT_INCREMENTAL) + c->prompt_inputcb(c, c->prompt_data, "=", 0); free(tmp); format_free(ft); @@ -599,6 +602,9 @@ status_prompt_clear(struct client *c) if (c->prompt_freecb != NULL && c->prompt_data != NULL) c->prompt_freecb(c->prompt_data); + free(c->prompt_last); + c->prompt_last = NULL; + free(c->prompt_string); c->prompt_string = NULL; @@ -1260,17 +1266,27 @@ process_key: status_prompt_clear(c); break; case '\022': /* C-r */ - if (c->prompt_flags & PROMPT_INCREMENTAL) { + if (~c->prompt_flags & PROMPT_INCREMENTAL) + break; + if (c->prompt_buffer[0].size == 0) { + prefix = '='; + free (c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(c->prompt_last); + c->prompt_index = utf8_strlen(c->prompt_buffer); + } else prefix = '-'; - goto changed; - } - break; + goto changed; case '\023': /* C-s */ - if (c->prompt_flags & PROMPT_INCREMENTAL) { + if (~c->prompt_flags & PROMPT_INCREMENTAL) + break; + if (c->prompt_buffer[0].size == 0) { + prefix = '='; + free (c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(c->prompt_last); + c->prompt_index = utf8_strlen(c->prompt_buffer); + } else prefix = '+'; - goto changed; - } - break; + goto changed; default: goto append_key; } diff --git a/window-copy.c b/window-copy.c index 4c81cb1f..bfa94aed 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2030,6 +2030,8 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) data->timeout = 0; + log_debug ("%s: %s", __func__, argument); + prefix = *argument++; if (data->searchx == -1 || data->searchy == -1) { data->searchx = data->cx; @@ -2083,6 +2085,8 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) data->timeout = 0; + log_debug ("%s: %s", __func__, argument); + prefix = *argument++; if (data->searchx == -1 || data->searchy == -1) { data->searchx = data->cx; From a75aca4d6ad71e45601b2b45f79036fae4f0d2a6 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 8 Jan 2021 10:09:44 +0000 Subject: [PATCH 31/90] Missed from last commit. --- tmux.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tmux.h b/tmux.h index 0defff34..826d8ea4 100644 --- a/tmux.h +++ b/tmux.h @@ -1693,6 +1693,7 @@ struct client { char *prompt_string; struct utf8_data *prompt_buffer; + char *prompt_last; size_t prompt_index; prompt_input_cb prompt_inputcb; prompt_free_cb prompt_freecb; From 71c590a37f98d82c72279eddae74f9b8be146202 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 17 Jan 2021 16:17:41 +0000 Subject: [PATCH 32/90] Add -N flag to never start server even if command would normally do so, GitHub issue 2523. --- client.c | 2 ++ tmux.1 | 5 +++++ tmux.c | 7 +++++-- tmux.h | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/client.c b/client.c index dcbc0d18..a3e300cd 100644 --- a/client.c +++ b/client.c @@ -127,6 +127,8 @@ retry: log_debug("connect failed: %s", strerror(errno)); if (errno != ECONNREFUSED && errno != ENOENT) goto failed; + if (flags & CLIENT_NOSTARTSERVER) + goto failed; if (~flags & CLIENT_STARTSERVER) goto failed; close(fd); diff --git a/tmux.1 b/tmux.1 index 941305e3..93842482 100644 --- a/tmux.1 +++ b/tmux.1 @@ -191,6 +191,11 @@ directories are missing). Behave as a login shell. This flag currently has no effect and is for compatibility with other shells when using tmux as a login shell. +.It Fl N +Do not start the server even if the command would normally do so (for example +.Ic new-session +or +.Ic start-server ) . .It Fl S Ar socket-path Specify a full alternative path to the server socket. If diff --git a/tmux.c b/tmux.c index b7fc5121..5861e66b 100644 --- a/tmux.c +++ b/tmux.c @@ -57,7 +57,7 @@ static __dead void usage(void) { fprintf(stderr, - "usage: %s [-2CDluvV] [-c shell-command] [-f file] [-L socket-name]\n" + "usage: %s [-2CDlNuvV] [-c shell-command] [-f file] [-L socket-name]\n" " [-S socket-path] [-T features] [command [flags]]\n", getprogname()); exit(1); @@ -350,7 +350,7 @@ main(int argc, char **argv) if (**argv == '-') flags = CLIENT_LOGIN; - while ((opt = getopt(argc, argv, "2c:CDdf:lL:qS:T:uUvV")) != -1) { + while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) { switch (opt) { case '2': tty_add_features(&feat, "256", ":,"); @@ -380,6 +380,9 @@ main(int argc, char **argv) free(label); label = xstrdup(optarg); break; + case 'N': + flags |= CLIENT_NOSTARTSERVER; + break; case 'q': break; case 'S': diff --git a/tmux.h b/tmux.h index 826d8ea4..832918e2 100644 --- a/tmux.h +++ b/tmux.h @@ -1635,7 +1635,7 @@ struct client { #define CLIENT_DEAD 0x200 #define CLIENT_REDRAWBORDERS 0x400 #define CLIENT_READONLY 0x800 -/* 0x1000 unused */ +#define CLIENT_NOSTARTSERVER 0x1000 #define CLIENT_CONTROL 0x2000 #define CLIENT_CONTROLCONTROL 0x4000 #define CLIENT_FOCUSED 0x8000 From a3011be0d267a090c8bfa01a4ebe093bc203a1c4 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 17:21:51 +0000 Subject: [PATCH 33/90] Look for libevent2 differently from libevent for platforms with both. --- alerts.c | 1 - client.c | 1 - compat.h | 13 ++++++++ configure.ac | 81 +++++++++++++++++++---------------------------- control.c | 1 - osdep-cygwin.c | 1 - osdep-darwin.c | 1 - osdep-dragonfly.c | 1 - osdep-freebsd.c | 1 - osdep-haiku.c | 1 - osdep-hpux.c | 2 -- osdep-linux.c | 1 - osdep-netbsd.c | 1 - osdep-openbsd.c | 3 +- osdep-sunos.c | 1 - osdep-unknown.c | 2 -- proc.c | 1 - screen-write.c | 1 - server-client.c | 1 - server.c | 1 - tmux.c | 1 - tmux.h | 1 - 22 files changed, 48 insertions(+), 70 deletions(-) diff --git a/alerts.c b/alerts.c index 0f2eb179..4cc5c3eb 100644 --- a/alerts.c +++ b/alerts.c @@ -18,7 +18,6 @@ #include -#include #include #include "tmux.h" diff --git a/client.c b/client.c index 757e4aa8..8b17aa22 100644 --- a/client.c +++ b/client.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include diff --git a/compat.h b/compat.h index 6e26a02c..828c956d 100644 --- a/compat.h +++ b/compat.h @@ -27,6 +27,19 @@ #include #include +#ifdef HAVE_EVENT2_EVENT_H +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + #ifdef HAVE_MALLOC_TRIM #include #endif diff --git a/configure.ac b/configure.ac index 8dd00d79..80e8be9e 100644 --- a/configure.ac +++ b/configure.ac @@ -182,88 +182,72 @@ AC_SEARCH_LIBS(clock_gettime, rt) # implementations. AC_LIBOBJ(getopt) -# Look for libevent. +# Look for libevent. Try libevent_core or libevent with pkg-config first then +# look for the library. PKG_CHECK_MODULES( LIBEVENT, - libevent, + [libevent_core >= 2 libevent >= 2], [ - AM_CFLAGS="$LIBEVENT_CFLAGS $AM_CFLAGS" - CFLAGS="$AM_CFLAGS $SAVED_CFLAGS" + AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" LIBS="$LIBEVENT_LIBS $LIBS" found_libevent=yes ], + found_libevent=no +) +if test x$found_libevent = xno; then + AC_SEARCH_LIBS( + event_init, + [event_core event event-1.4], + found_libevent=yes, + found_libevent=no + ) +fi +AC_CHECK_HEADER( + event2/event.h, + AC_DEFINE(HAVE_EVENT2_EVENT_H), [ - AC_SEARCH_LIBS( - event_init, - [event event-1.4 event2], - found_libevent=yes, + AC_CHECK_HEADER( + event.h, + AC_DEFINE(HAVE_EVENT_H), found_libevent=no ) ] ) -AC_CHECK_HEADER( - event.h, - , - found_libevent=no -) if test "x$found_libevent" = xno; then AC_MSG_ERROR("libevent not found") fi -# Look for ncurses. +# Look for ncurses or curses. Try pkg-config first then directly for the +# library. PKG_CHECK_MODULES( - LIBTINFO, - tinfo, + LIBNCURSES, + [tinfo ncurses ncursesw], found_ncurses=yes, found_ncurses=no ) -if test "x$found_ncurses" = xno; then - PKG_CHECK_MODULES( - LIBNCURSES, - ncurses, - found_ncurses=yes, - found_ncurses=no - ) -fi -if test "x$found_ncurses" = xno; then - PKG_CHECK_MODULES( - LIBNCURSES, - ncursesw, - found_ncurses=yes, - found_ncurses=no - ) -fi if test "x$found_ncurses" = xyes; then - AM_CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CFLAGS" - CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $CFLAGS" + AM_CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $SAVED_CPPFLAGS" LIBS="$LIBNCURSES_LIBS $LIBTINFO_LIBS $LIBS" else - # pkg-config didn't work, try ncurses. - AC_CHECK_LIB( - tinfo, + AC_SEARCH_LIBS( + [tinfo ncurses ncursesw], setupterm, found_ncurses=yes, found_ncurses=no ) - if test "x$found_ncurses" = xno; then - AC_CHECK_LIB( - ncurses, - setupterm, - found_ncurses=yes, - found_ncurses=no - ) - fi if test "x$found_ncurses" = xyes; then AC_CHECK_HEADER( ncurses.h, LIBS="$LIBS -lncurses", - found_ncurses=no) + found_ncurses=no + ) fi fi if test "x$found_ncurses" = xyes; then AC_DEFINE(HAVE_NCURSES_H) else - # No ncurses, try curses. AC_CHECK_LIB( curses, setupterm, @@ -273,7 +257,8 @@ else AC_CHECK_HEADER( curses.h, , - found_curses=no) + found_curses=no + ) if test "x$found_curses" = xyes; then LIBS="$LIBS -lcurses" AC_DEFINE(HAVE_CURSES_H) diff --git a/control.c b/control.c index e86429cf..7a5b9eb2 100644 --- a/control.c +++ b/control.c @@ -19,7 +19,6 @@ #include -#include #include #include #include diff --git a/osdep-cygwin.c b/osdep-cygwin.c index 60630b33..4346373c 100644 --- a/osdep-cygwin.c +++ b/osdep-cygwin.c @@ -19,7 +19,6 @@ #include #include -#include #include #include #include diff --git a/osdep-darwin.c b/osdep-darwin.c index 6b2b1d72..85e5470b 100644 --- a/osdep-darwin.c +++ b/osdep-darwin.c @@ -20,7 +20,6 @@ #include #include -#include #include #include #include diff --git a/osdep-dragonfly.c b/osdep-dragonfly.c index 879034e8..02a4d18e 100644 --- a/osdep-dragonfly.c +++ b/osdep-dragonfly.c @@ -23,7 +23,6 @@ #include #include -#include #include #include #include diff --git a/osdep-freebsd.c b/osdep-freebsd.c index a7d02930..0f347f9a 100644 --- a/osdep-freebsd.c +++ b/osdep-freebsd.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include diff --git a/osdep-haiku.c b/osdep-haiku.c index 298dc05e..7b1f800a 100644 --- a/osdep-haiku.c +++ b/osdep-haiku.c @@ -18,7 +18,6 @@ #include -#include #include #include diff --git a/osdep-hpux.c b/osdep-hpux.c index 16993b93..64e33784 100644 --- a/osdep-hpux.c +++ b/osdep-hpux.c @@ -18,8 +18,6 @@ #include -#include - #include "tmux.h" char * diff --git a/osdep-linux.c b/osdep-linux.c index 5f0d9352..7dbab1f0 100644 --- a/osdep-linux.c +++ b/osdep-linux.c @@ -20,7 +20,6 @@ #include #include -#include #include #include #include diff --git a/osdep-netbsd.c b/osdep-netbsd.c index 67894175..b473e017 100644 --- a/osdep-netbsd.c +++ b/osdep-netbsd.c @@ -22,7 +22,6 @@ #include #include -#include #include #include #include diff --git a/osdep-openbsd.c b/osdep-openbsd.c index b21a6628..f5c61372 100644 --- a/osdep-openbsd.c +++ b/osdep-openbsd.c @@ -23,11 +23,12 @@ #include #include -#include #include #include #include +#include "compat.h" + #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif diff --git a/osdep-sunos.c b/osdep-sunos.c index 07043a9b..138e6bad 100644 --- a/osdep-sunos.c +++ b/osdep-sunos.c @@ -19,7 +19,6 @@ #include #include -#include #include #include #include diff --git a/osdep-unknown.c b/osdep-unknown.c index bc59f569..440f619e 100644 --- a/osdep-unknown.c +++ b/osdep-unknown.c @@ -18,8 +18,6 @@ #include -#include - #include "tmux.h" char * diff --git a/proc.c b/proc.c index 592d8e05..6d64b0bb 100644 --- a/proc.c +++ b/proc.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include diff --git a/screen-write.c b/screen-write.c index 92f1aa39..f374630c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -259,7 +259,6 @@ screen_write_start_callback(struct screen_write_ctx *ctx, struct screen *s, } } - /* Initialize writing. */ void screen_write_start(struct screen_write_ctx *ctx, struct screen *s) diff --git a/server-client.c b/server-client.c index 451fe2eb..75a5719c 100644 --- a/server-client.c +++ b/server-client.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include diff --git a/server.c b/server.c index 9a1875f4..6aee61fc 100644 --- a/server.c +++ b/server.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include diff --git a/tmux.c b/tmux.c index 066714df..96b94e01 100644 --- a/tmux.c +++ b/tmux.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include diff --git a/tmux.h b/tmux.h index 2fdf7eb6..0d7d8f48 100644 --- a/tmux.h +++ b/tmux.h @@ -22,7 +22,6 @@ #include #include -#include #include #include #include From 603280cb286350c628ac2dfc06d5b8de135aa2af Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 17:52:10 +0000 Subject: [PATCH 34/90] +compat.h --- osdep-darwin.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osdep-darwin.c b/osdep-darwin.c index 85e5470b..a2b125ad 100644 --- a/osdep-darwin.c +++ b/osdep-darwin.c @@ -25,6 +25,8 @@ #include #include +#include "compat.h" + char *osdep_get_name(int, char *); char *osdep_get_cwd(int); struct event_base *osdep_event_init(void); From d4866d5fe6214064882244ddb32f05480e9d8d91 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 17:55:14 +0000 Subject: [PATCH 35/90] Fix SEARCH_LIBS. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 80e8be9e..1bd91eb4 100644 --- a/configure.ac +++ b/configure.ac @@ -232,8 +232,8 @@ if test "x$found_ncurses" = xyes; then LIBS="$LIBNCURSES_LIBS $LIBTINFO_LIBS $LIBS" else AC_SEARCH_LIBS( - [tinfo ncurses ncursesw], setupterm, + [tinfo ncurses ncursesw], found_ncurses=yes, found_ncurses=no ) From c6bcf3dba52fe0f5e161a9a7cedaae27c7a30845 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 18:19:50 +0000 Subject: [PATCH 36/90] Fix yes/no for b64_ntop check. --- configure.ac | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 1bd91eb4..e70e3147 100644 --- a/configure.ac +++ b/configure.ac @@ -324,6 +324,7 @@ AC_TRY_LINK( found_b64_ntop=yes, found_b64_ntop=no ) +AC_MSG_RESULT($found_b64_ntop) OLD_LIBS="$LIBS" if test "x$found_b64_ntop" = xno; then AC_MSG_RESULT(no) @@ -339,6 +340,7 @@ if test "x$found_b64_ntop" = xno; then found_b64_ntop=yes, found_b64_ntop=no ) + AC_MSG_RESULT($found_b64_ntop) fi if test "x$found_b64_ntop" = xno; then AC_MSG_CHECKING(for b64_ntop with -lnetwork) @@ -353,14 +355,13 @@ if test "x$found_b64_ntop" = xno; then found_b64_ntop=yes, found_b64_ntop=no ) + AC_MSG_RESULT($found_b64_ntop) fi if test "x$found_b64_ntop" = xyes; then AC_DEFINE(HAVE_B64_NTOP) - AC_MSG_RESULT(yes) else LIBS="$OLD_LIBS" AC_LIBOBJ(base64) - AC_MSG_RESULT(no) fi # Look for networking libraries. From 032723c8740710cd34bdf6e7a0124f8fb18f6d70 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 18:21:54 +0000 Subject: [PATCH 37/90] Set CFLAGS also. --- configure.ac | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configure.ac b/configure.ac index e70e3147..d18a584d 100644 --- a/configure.ac +++ b/configure.ac @@ -190,6 +190,8 @@ PKG_CHECK_MODULES( [ AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" + AM_CFLAGS="$LIBEVENT_CFLAGS $AM_CFLAGS" + CFLAGS="$AM_CFLAGS $SAVED_CFLAGS" LIBS="$LIBEVENT_LIBS $LIBS" found_libevent=yes ], @@ -229,6 +231,8 @@ PKG_CHECK_MODULES( if test "x$found_ncurses" = xyes; then AM_CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $SAVED_CPPFLAGS" + AM_CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CFLAGS" + CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $SAVED_CFLAGS" LIBS="$LIBNCURSES_LIBS $LIBTINFO_LIBS $LIBS" else AC_SEARCH_LIBS( From b18834be8aadbf133206e5db256d76acac398da7 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 18:24:52 +0000 Subject: [PATCH 38/90] Revert "Set CFLAGS also." This reverts commit 032723c8740710cd34bdf6e7a0124f8fb18f6d70. --- configure.ac | 4 ---- 1 file changed, 4 deletions(-) diff --git a/configure.ac b/configure.ac index d18a584d..e70e3147 100644 --- a/configure.ac +++ b/configure.ac @@ -190,8 +190,6 @@ PKG_CHECK_MODULES( [ AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" - AM_CFLAGS="$LIBEVENT_CFLAGS $AM_CFLAGS" - CFLAGS="$AM_CFLAGS $SAVED_CFLAGS" LIBS="$LIBEVENT_LIBS $LIBS" found_libevent=yes ], @@ -231,8 +229,6 @@ PKG_CHECK_MODULES( if test "x$found_ncurses" = xyes; then AM_CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $SAVED_CPPFLAGS" - AM_CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CFLAGS" - CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $SAVED_CFLAGS" LIBS="$LIBNCURSES_LIBS $LIBTINFO_LIBS $LIBS" else AC_SEARCH_LIBS( From 607594f6e5836f2060aec690b49791ea1ef982d9 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 18:47:14 +0000 Subject: [PATCH 39/90] Show config.log on failure. --- .github/travis/build-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/travis/build-all.sh b/.github/travis/build-all.sh index 00f8f522..883868e8 100644 --- a/.github/travis/build-all.sh +++ b/.github/travis/build-all.sh @@ -35,4 +35,4 @@ tar -zxf ncurses-*.tar.gz || exit 1 sh autogen.sh || exit 1 PKG_CONFIG_PATH=$BUILD/lib/pkgconfig ./configure --prefix=$BUILD "$@" -make && make install || exit 1 +make && make install || (cat config.log; exit 1) From 4148417a2a0fdf2cc839568185150bb4a9203d5d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 17 Jan 2021 19:03:18 +0000 Subject: [PATCH 40/90] PKG_CHECK_MODULES needs to be separate. --- configure.ac | 64 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index e70e3147..a2591857 100644 --- a/configure.ac +++ b/configure.ac @@ -185,16 +185,29 @@ AC_LIBOBJ(getopt) # Look for libevent. Try libevent_core or libevent with pkg-config first then # look for the library. PKG_CHECK_MODULES( - LIBEVENT, - [libevent_core >= 2 libevent >= 2], + LIBEVENT_CORE, + [libevent_core >= 2], [ - AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS" + AM_CPPFLAGS="$LIBEVENT_CORE_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" - LIBS="$LIBEVENT_LIBS $LIBS" + LIBS="$LIBEVENT_CORE_LIBS $LIBS" found_libevent=yes ], found_libevent=no ) +if test x$found_libevent = xno; then + PKG_CHECK_MODULES( + LIBEVENT, + [libevent >= 2], + [ + AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBEVENT_LIBS $LIBS" + found_libevent=yes + ], + found_libevent=no + ) +fi if test x$found_libevent = xno; then AC_SEARCH_LIBS( event_init, @@ -221,16 +234,43 @@ fi # Look for ncurses or curses. Try pkg-config first then directly for the # library. PKG_CHECK_MODULES( - LIBNCURSES, - [tinfo ncurses ncursesw], - found_ncurses=yes, + LIBTINFO, + tinfo, + [ + AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBTINFO_LIBS $LIBS" + found_ncurses=yes + ], found_ncurses=no ) -if test "x$found_ncurses" = xyes; then - AM_CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CPPFLAGS" - CPPFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $SAVED_CPPFLAGS" - LIBS="$LIBNCURSES_LIBS $LIBTINFO_LIBS $LIBS" -else +if test "x$found_ncurses" = xno; then + PKG_CHECK_MODULES( + LIBNCURSES, + ncurses, + [ + AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBNCURSES_LIBS $LIBS" + found_ncurses=yes + ], + found_ncurses=no + ) +fi +if test "x$found_ncurses" = xno; then + PKG_CHECK_MODULES( + LIBNCURSESW, + ncursesw, + [ + AM_CPPFLAGS="$LIBNCURSESW_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBNCURSESW_LIBS $LIBS" + found_ncurses=yes + ], + found_ncurses=no + ) +fi +if test "x$found_ncurses" = xno; then AC_SEARCH_LIBS( setupterm, [tinfo ncurses ncursesw], From 91d112bf12789da07e25ed001f7961b1d6bd7a76 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 18 Jan 2021 10:27:54 +0000 Subject: [PATCH 41/90] There is no need to clear every line entirely before drawing to it, this means moving the cursor and messes up wrapping. Better to just clear the sections that aren't written over. GitHub issue 2537. --- grid.c | 1 - screen-write.c | 32 +++++++++++++++++++++++++------- tmux.h | 1 - tty.c | 13 +------------ 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/grid.c b/grid.c index 96302fc3..cce304c9 100644 --- a/grid.c +++ b/grid.c @@ -698,7 +698,6 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg) gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED; } - /* Move a group of cells. */ void grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx, diff --git a/screen-write.c b/screen-write.c index 92f1aa39..7df5cd92 100644 --- a/screen-write.c +++ b/screen-write.c @@ -259,7 +259,6 @@ screen_write_start_callback(struct screen_write_ctx *ctx, struct screen *s, } } - /* Initialize writing. */ void screen_write_start(struct screen_write_ctx *ctx, struct screen *s) @@ -1517,6 +1516,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_collect_item *ci, *tmp; struct screen_write_collect_line *cl; u_int y, cx, cy, items = 0; + int clear = 0; struct tty_ctx ttyctx; size_t written = 0; @@ -1540,22 +1540,29 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, cx = s->cx; cy = s->cy; for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; - if (cl->bg != 0) { - screen_write_set_cursor(ctx, 0, y); - screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.bg = cl->bg - 1; - tty_write(tty_cmd_clearline, &ttyctx); - } TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + if (clear != -1 && + (u_int)clear != ci->x && + cl->bg != 0) { + screen_write_set_cursor(ctx, clear, y); + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.bg = cl->bg - 1; + ttyctx.num = ci->x - clear; + log_debug("clear %u at %u", ttyctx.num, clear); + tty_write(tty_cmd_clearcharacter, &ttyctx); + } + screen_write_set_cursor(ctx, ci->x, y); if (ci->type == CLEAR_END) { screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; tty_write(tty_cmd_clearendofline, &ttyctx); + clear = -1; } else if (ci->type == CLEAR_START) { screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; tty_write(tty_cmd_clearstartofline, &ttyctx); + clear = ci->x + 1; } else { screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = &ci->gc; @@ -1563,6 +1570,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.ptr = cl->data + ci->x; ttyctx.num = ci->used; tty_write(tty_cmd_cells, &ttyctx); + clear = ci->x + ci->used; } items++; @@ -1571,6 +1579,16 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, TAILQ_REMOVE(&cl->items, ci, entry); free(ci); } + if (clear != -1 && + (u_int)clear != screen_size_x(s) - 1 && + cl->bg != 0) { + screen_write_set_cursor(ctx, clear, y); + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.bg = cl->bg - 1; + log_debug("clear to end at %u", clear); + tty_write(tty_cmd_clearendofline, &ttyctx); + } + clear = 0; cl->bg = 0; } s->cx = cx; s->cy = cy; diff --git a/tmux.h b/tmux.h index 832918e2..32f7fec7 100644 --- a/tmux.h +++ b/tmux.h @@ -1890,7 +1890,6 @@ const char *find_home(void); const char *getversion(void); void expand_paths(const char *, char ***, u_int *); - /* proc.c */ struct imsg; int proc_send(struct tmuxpeer *, enum msgtype, int, const void *, size_t); diff --git a/tty.c b/tty.c index fac7a99e..279bafaf 100644 --- a/tty.c +++ b/tty.c @@ -1531,20 +1531,9 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) { - if (ctx->bigger) { - tty_draw_pane(tty, ctx, ctx->ocy); - return; - } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); - - if (tty_term_has(tty->term, TTYC_ECH) && - !tty_fake_bce(tty, &ctx->defaults, 8)) - tty_putcode1(tty, TTYC_ECH, ctx->num); - else - tty_repeat_space(tty, ctx->num); + tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg); } void From 63f4a3c4e53b7d6aeb09917c0971af2df6549f31 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 18 Jan 2021 10:48:49 +0000 Subject: [PATCH 42/90] Extra result message. --- configure.ac | 1 - 1 file changed, 1 deletion(-) diff --git a/configure.ac b/configure.ac index a2591857..269dd5aa 100644 --- a/configure.ac +++ b/configure.ac @@ -367,7 +367,6 @@ AC_TRY_LINK( AC_MSG_RESULT($found_b64_ntop) OLD_LIBS="$LIBS" if test "x$found_b64_ntop" = xno; then - AC_MSG_RESULT(no) AC_MSG_CHECKING(for b64_ntop with -lresolv) LIBS="$OLD_LIBS -lresolv" AC_TRY_LINK( From 0730dce5abf5e43f8e3820a1d4e8754e61874a3d Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 18 Jan 2021 11:14:23 +0000 Subject: [PATCH 43/90] Hide some warnings on newer GCC versions, GitHUb issue 2525. --- options.c | 7 ++++--- server-client.c | 8 ++++---- window-copy.c | 37 +++++++++++++++++-------------------- window-customize.c | 10 +++------- 4 files changed, 28 insertions(+), 34 deletions(-) diff --git a/options.c b/options.c index 09850f7e..9bc89db3 100644 --- a/options.c +++ b/options.c @@ -157,8 +157,7 @@ options_value_to_string(struct options_entry *o, union options_value *ov, case OPTIONS_TABLE_CHOICE: s = xstrdup(o->tableentry->choices[ov->number]); break; - case OPTIONS_TABLE_STRING: - case OPTIONS_TABLE_COMMAND: + default: fatalx("not a number option type"); } return (s); @@ -311,6 +310,8 @@ options_default_to_string(const struct options_table_entry *oe) case OPTIONS_TABLE_CHOICE: s = xstrdup(oe->choices[oe->default_num]); break; + default: + fatalx("unknown option type"); } return (s); } @@ -703,7 +704,7 @@ options_get_number(struct options *oo, const char *name) if (o == NULL) fatalx("missing option %s", name); if (!OPTIONS_IS_NUMBER(o)) - fatalx("option %s is not a number", name); + fatalx("option %s is not a number", name); return (o->value.number); } diff --git a/server-client.c b/server-client.c index f85304a6..a55188d1 100644 --- a/server-client.c +++ b/server-client.c @@ -1781,11 +1781,11 @@ server_client_check_exit(struct client *c) switch (c->exit_type) { case CLIENT_EXIT_RETURN: - if (c->exit_message != NULL) { + if (c->exit_message != NULL) msize = strlen(c->exit_message) + 1; - size = (sizeof c->retval) + msize; - } else - size = (sizeof c->retval); + else + msize = 0; + size = (sizeof c->retval) + msize; data = xmalloc(size); memcpy(data, &c->retval, sizeof c->retval); if (c->exit_message != NULL) diff --git a/window-copy.c b/window-copy.c index bfa94aed..889a536d 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3453,10 +3453,10 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, struct window_copy_mode_data *data = wme->data; u_int xx, yy; + xx = data->cx; yy = screen_hsize(data->backing) + data->cy - data->oy; switch (data->selflag) { case SEL_WORD: - xx = data->cx; if (no_reset) break; begin = 0; @@ -3482,10 +3482,8 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, } break; case SEL_LINE: - if (no_reset) { - xx = data->cx; + if (no_reset) break; - } begin = 0; if (data->dy > yy) { /* Right to left selection. */ @@ -3505,7 +3503,6 @@ window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, } break; case SEL_CHAR: - xx = data->cx; break; } if (begin) { @@ -4784,22 +4781,22 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) if (x < data->selrx || x > data->endselrx || yg != data->selry) data->selflag = SEL_CHAR; switch (data->selflag) { - case SEL_WORD: - if (data->ws != NULL) { - window_copy_update_cursor(wme, x, y); - window_copy_cursor_previous_word_pos(wme, - data->ws, 0, &x, &y); - y -= screen_hsize(data->backing) - data->oy; - } + case SEL_WORD: + if (data->ws != NULL) { window_copy_update_cursor(wme, x, y); - break; - case SEL_LINE: - window_copy_update_cursor(wme, 0, y); - break; - case SEL_CHAR: - window_copy_update_cursor(wme, x, y); - window_copy_start_selection(wme); - break; + window_copy_cursor_previous_word_pos(wme, data->ws, 0, + &x, &y); + y -= screen_hsize(data->backing) - data->oy; + } + window_copy_update_cursor(wme, x, y); + break; + case SEL_LINE: + window_copy_update_cursor(wme, 0, y); + break; + case SEL_CHAR: + window_copy_update_cursor(wme, x, y); + window_copy_start_selection(wme); + break; } window_copy_redraw_screen(wme); diff --git a/window-customize.c b/window-customize.c index 1dad07cd..a1f191b5 100644 --- a/window-customize.c +++ b/window-customize.c @@ -190,13 +190,6 @@ window_customize_scope_text(enum window_customize_scope scope, u_int idx; switch (scope) { - case WINDOW_CUSTOMIZE_NONE: - case WINDOW_CUSTOMIZE_KEY: - case WINDOW_CUSTOMIZE_SERVER: - case WINDOW_CUSTOMIZE_GLOBAL_SESSION: - case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: - s = xstrdup(""); - break; case WINDOW_CUSTOMIZE_PANE: window_pane_index(fs->wp, &idx); xasprintf(&s, "pane %u", idx); @@ -207,6 +200,9 @@ window_customize_scope_text(enum window_customize_scope scope, case WINDOW_CUSTOMIZE_WINDOW: xasprintf(&s, "window %u", fs->wl->idx); break; + default: + s = xstrdup(""); + break; } return (s); } From 3c86fa2ad0b7815c8f26618aefee24cb6d17cddb Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 18 Jan 2021 11:14:37 +0000 Subject: [PATCH 44/90] Add -Wno-format-y2k. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index e9b1cb15..3e15204f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,7 +28,7 @@ AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wcast-align AM_CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes -AM_CFLAGS += -Wno-unused-result +AM_CFLAGS += -Wno-unused-result -Wno-format-y2k AM_CPPFLAGS += -DDEBUG endif AM_CPPFLAGS += -iquote. From fb774b77d0f5ccb988b508b8a794633d4c9a5962 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 20 Jan 2021 07:16:54 +0000 Subject: [PATCH 45/90] Change so that window_flags escapes # automatically which means configs will not have to change. A new format window_raw_flags contains the old unescaped version. --- cmd-list-windows.c | 4 ++-- control-notify.c | 2 +- format.c | 30 ++++++++++++++++-------------- options-table.c | 4 ++-- tmux.1 | 7 ++++--- tmux.h | 2 +- window.c | 7 +++++-- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/cmd-list-windows.c b/cmd-list-windows.c index 9c33c2d0..d6cc0b7a 100644 --- a/cmd-list-windows.c +++ b/cmd-list-windows.c @@ -28,14 +28,14 @@ */ #define LIST_WINDOWS_TEMPLATE \ - "#{window_index}: #{window_name}#{window_flags} " \ + "#{window_index}: #{window_name}#{window_raw_flags} " \ "(#{window_panes} panes) " \ "[#{window_width}x#{window_height}] " \ "[layout #{window_layout}] #{window_id}" \ "#{?window_active, (active),}"; #define LIST_WINDOWS_WITH_SESSION_TEMPLATE \ "#{session_name}:" \ - "#{window_index}: #{window_name}#{window_flags} " \ + "#{window_index}: #{window_name}#{window_raw_flags} " \ "(#{window_panes} panes) " \ "[#{window_width}x#{window_height}] " diff --git a/control-notify.c b/control-notify.c index a1735d57..cc706ac2 100644 --- a/control-notify.c +++ b/control-notify.c @@ -49,7 +49,7 @@ control_notify_window_layout_changed(struct window *w) char *cp; template = "%layout-change #{window_id} #{window_layout} " - "#{window_visible_layout} #{window_flags}"; + "#{window_visible_layout} #{window_raw_flags}"; TAILQ_FOREACH(c, &clients, entry) { if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) diff --git a/format.c b/format.c index 2d7402af..df1898a6 100644 --- a/format.c +++ b/format.c @@ -89,7 +89,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_TIMESTRING 0x1 #define FORMAT_BASENAME 0x2 #define FORMAT_DIRNAME 0x4 -#define FORMAT_QUOTE 0x8 +#define FORMAT_QUOTE_SHELL 0x8 #define FORMAT_LITERAL 0x10 #define FORMAT_EXPAND 0x20 #define FORMAT_EXPANDTIME 0x40 @@ -99,7 +99,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_PRETTY 0x400 #define FORMAT_LENGTH 0x800 #define FORMAT_WIDTH 0x1000 -#define FORMAT_ESCAPE 0x2000 +#define FORMAT_QUOTE_STYLE 0x2000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 10 @@ -1378,9 +1378,9 @@ format_add_cb(struct format_tree *ft, const char *key, format_cb cb) fe->value = NULL; } -/* Quote special characters in string. */ +/* Quote shell special characters in string. */ static char * -format_quote(const char *s) +format_quote_shell(const char *s) { const char *cp; char *out, *at; @@ -1395,9 +1395,9 @@ format_quote(const char *s) return (out); } -/* Escape #s in string. */ +/* Quote #s in string. */ static char * -format_escape(const char *s) +format_quote_style(const char *s) { const char *cp; char *out, *at; @@ -1552,14 +1552,14 @@ found: found = xstrdup(dirname(saved)); free(saved); } - if (modifiers & FORMAT_QUOTE) { + if (modifiers & FORMAT_QUOTE_SHELL) { saved = found; - found = xstrdup(format_quote(saved)); + found = xstrdup(format_quote_shell(saved)); free(saved); } - if (modifiers & FORMAT_ESCAPE) { + if (modifiers & FORMAT_QUOTE_STYLE) { saved = found; - found = xstrdup(format_escape(saved)); + found = xstrdup(format_quote_style(saved)); free(saved); } return (found); @@ -2240,9 +2240,10 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, break; case 'q': if (fm->argc < 1) - modifiers |= FORMAT_QUOTE; - else if (strchr(fm->argv[0], 'e') != NULL) - modifiers |= FORMAT_ESCAPE; + modifiers |= FORMAT_QUOTE_SHELL; + else if (strchr(fm->argv[0], 'e') != NULL || + strchr(fm->argv[0], 'h') != NULL) + modifiers |= FORMAT_QUOTE_STYLE; break; case 'E': modifiers |= FORMAT_EXPAND; @@ -2980,7 +2981,8 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) format_add(ft, "window_index", "%d", wl->idx); 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_flags", "%s", window_printable_flags(wl, 1)); + format_add(ft, "window_raw_flags", "%s", window_printable_flags(wl, 0)); format_add(ft, "window_active", "%d", wl == s->curw); format_add_cb(ft, "window_active_sessions", format_cb_window_active_sessions); diff --git a/options-table.c b/options-table.c index a8276c86..74a59134 100644 --- a/options-table.c +++ b/options-table.c @@ -1018,7 +1018,7 @@ const struct options_table_entry options_table[] = { { .name = "window-status-current-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{q/e:window_flags}, }", + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", .text = "Format of the current window in the status line." }, @@ -1034,7 +1034,7 @@ const struct options_table_entry options_table[] = { { .name = "window-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{q/e:window_flags}, }", + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", .text = "Format of windows in the status line, except the current " "window." }, diff --git a/tmux.1 b/tmux.1 index 93842482..8c812f38 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4651,8 +4651,8 @@ of the variable respectively. .Ql q:\& will escape .Xr sh 1 -special characters or with an -.Ql e +special characters or with a +.Ql h suffix, escape hash characters (so .Ql # becomes @@ -4882,7 +4882,8 @@ The following variables are available, where appropriate: .It Li "window_cell_height" Ta "" Ta "Height of each cell in pixels" .It Li "window_cell_width" Ta "" Ta "Width of each cell in pixels" .It Li "window_end_flag" Ta "" Ta "1 if window has the highest index" -.It Li "window_flags" Ta "#F" Ta "Window flags" +.It Li "window_flags" Ta "#F" Ta "Window flags with # escaped as ##" +.It Li "window_raw_flags" Ta "" Ta "Window flags with nothing escaped" .It Li "window_format" Ta "" Ta "1 if format is for a window" .It Li "window_height" Ta "" Ta "Height of window" .It Li "window_id" Ta "" Ta "Unique window ID" diff --git a/tmux.h b/tmux.h index 32f7fec7..c593a175 100644 --- a/tmux.h +++ b/tmux.h @@ -2761,7 +2761,7 @@ int window_pane_key(struct window_pane *, struct client *, int window_pane_visible(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, int); -const char *window_printable_flags(struct winlink *); +const char *window_printable_flags(struct winlink *, int); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); diff --git a/window.c b/window.c index 22986a04..6bfdb1cd 100644 --- a/window.c +++ b/window.c @@ -803,15 +803,18 @@ window_destroy_panes(struct window *w) } const char * -window_printable_flags(struct winlink *wl) +window_printable_flags(struct winlink *wl, int escape) { struct session *s = wl->session; static char flags[32]; int pos; pos = 0; - if (wl->flags & WINLINK_ACTIVITY) + if (wl->flags & WINLINK_ACTIVITY) { flags[pos++] = '#'; + if (escape) + flags[pos++] = '#'; + } if (wl->flags & WINLINK_BELL) flags[pos++] = '!'; if (wl->flags & WINLINK_SILENCE) From 8d185395e479d8e7792a56bc61415bef640cf4b7 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 22 Jan 2021 10:21:24 +0000 Subject: [PATCH 46/90] Fix some cursor movement commands, from Anindya Mukherjee. --- window-copy.c | 98 +++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/window-copy.c b/window-copy.c index 889a536d..a2f4b2e5 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3981,11 +3981,12 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, yy, ny, hsize; + u_int px, py, cy, oldy, yy, ny, nd, hsize; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; + oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_start_of_line(&gr, 1); @@ -3996,9 +3997,11 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) if (py < yy) { ny = yy - py; cy = 0; + nd = 1; } else { ny = 0; cy = py - yy; + nd = oldy - cy + 1; } while (ny > 0) { window_copy_cursor_up(wme, 1); @@ -4006,7 +4009,7 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) } window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_redraw_lines(wme, data->cy, nd); } static void @@ -4038,11 +4041,12 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, yy, ny, hsize; + u_int px, py, cy, oldy, yy, ny, nd, hsize; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; + oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); if (data->screen.sel != NULL && data->rectflag) @@ -4054,10 +4058,14 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) /* Scroll down if we went off the visible screen. */ cy = py - hsize + data->oy; yy = screen_size_y(back_s) - 1; - if (cy > yy) + if (cy > yy) { ny = cy - yy; - else + oldy = yy; + nd = 1; + } else { ny = 0; + nd = cy - oldy + 1; + } while (ny > 0) { window_copy_cursor_down(wme, 1); ny--; @@ -4067,7 +4075,7 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) else window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_redraw_lines(wme, oldy, nd); } static void @@ -4433,41 +4441,39 @@ window_copy_cursor_next_word(struct window_mode_entry *wme, { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - u_int px, py, xx, yy; - int expected = 0; + struct grid_reader gr; + u_int px, py, cy, oldy, yy, ny, nd, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - yy = screen_hsize(back_s) + screen_size_y(back_s) - 1; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - /* - * First skip past any nonword characters and then any word characters. - * - * expected is initially set to 0 for the former and then 1 for the - * latter. - */ - do { - while (px > xx || - window_copy_in_set(wme, px, py, separators) == expected) { - /* Move down if we're past the end of the line. */ - if (px > xx) { - if (py == yy) - return; - window_copy_cursor_down(wme, 0); - px = 0; + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_next_word(&gr, separators); + grid_reader_get_cursor(&gr, &px, &py); - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - } else - px++; - } - expected = !expected; - } while (expected == 1); - - window_copy_update_cursor(wme, px, data->cy); + /* Scroll down if we went off the visible screen. */ + cy = py - hsize + data->oy; + yy = screen_size_y(back_s) - 1; + if (cy > yy) { + ny = cy - yy; + oldy = yy; + nd = 1; + } else { + ny = 0; + nd = cy - oldy + 1; + } + while (ny > 0) { + window_copy_cursor_down(wme, 1); + ny--; + } + if (cy > yy) + window_copy_update_cursor(wme, px, yy); + else + window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_redraw_lines(wme, oldy, nd); } static void @@ -4528,12 +4534,13 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, struct options *oo = wp->window->options; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, yy, ny, hsize; + u_int px, py, cy, oldy, yy, ny, nd, hsize; int keys; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; + oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); keys = options_get_number(oo, "mode-keys"); @@ -4547,10 +4554,14 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, /* Scroll down if we went off the visible screen. */ cy = py - hsize + data->oy; yy = screen_size_y(back_s) - 1; - if (cy > yy) + if (cy > yy) { ny = cy - yy; - else + oldy = yy; + nd = 1; + } else { ny = 0; + nd = cy - oldy + 1; + } while (ny > 0) { window_copy_cursor_down(wme, 1); ny--; @@ -4560,7 +4571,7 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, else window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, no_reset)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_redraw_lines(wme, oldy, nd); } /* Compute the previous place where a word begins. */ @@ -4617,11 +4628,12 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, yy, ny, hsize; + u_int px, py, cy, oldy, yy, ny, nd, hsize; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; + oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_previous_word(&gr, separators, already); @@ -4632,9 +4644,11 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, if (py < yy) { ny = yy - py; cy = 0; + nd = 1; } else { ny = 0; cy = py - yy; + nd = oldy - cy + 1; } while (ny > 0) { window_copy_cursor_up(wme, 1); @@ -4642,7 +4656,7 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, } window_copy_update_cursor(wme, px, cy); if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_redraw_lines(wme, data->cy, nd); } static void From bba71f696f49cdd3f70eaea12fd3a34c407a5aa3 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 22 Jan 2021 10:24:52 +0000 Subject: [PATCH 47/90] Add rectangle-on and rectangle-off copy mode commands, GitHub isse 2546 from author at will dot party. --- tmux.1 | 2 ++ window-copy.c | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/tmux.1 b/tmux.1 index 8c812f38..160e6a92 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1657,6 +1657,8 @@ The following commands are supported in copy mode: .It Li "previous-paragraph" Ta "{" Ta "M-{" .It Li "previous-space" Ta "B" Ta "" .It Li "previous-word" Ta "b" Ta "M-b" +.It Li "rectangle-on" Ta "" Ta "" +.It Li "rectangle-off" Ta "" Ta "" .It Li "rectangle-toggle" Ta "v" Ta "R" .It Li "refresh-from-pane" Ta "r" Ta "r" .It Li "scroll-down" Ta "C-e" Ta "C-Down" diff --git a/window-copy.c b/window-copy.c index a2f4b2e5..89b2a9af 100644 --- a/window-copy.c +++ b/window-copy.c @@ -128,7 +128,7 @@ static void window_copy_cursor_previous_word(struct window_mode_entry *, const char *, int); static void window_copy_scroll_up(struct window_mode_entry *, u_int); static void window_copy_scroll_down(struct window_mode_entry *, u_int); -static void window_copy_rectangle_toggle(struct window_mode_entry *); +static void window_copy_rectangle_set(struct window_mode_entry *, int); static void window_copy_move_mouse(struct mouse_event *); static void window_copy_drag_update(struct client *, struct mouse_event *); static void window_copy_drag_release(struct client *, struct mouse_event *); @@ -1625,6 +1625,30 @@ window_copy_cmd_previous_word(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_rectangle_on(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->lineflag = LINE_SEL_NONE; + window_copy_rectangle_set(wme, 1); + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_rectangle_off(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->lineflag = LINE_SEL_NONE; + window_copy_rectangle_set(wme, 0); + + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_rectangle_toggle(struct window_copy_cmd_state *cs) { @@ -1632,7 +1656,7 @@ window_copy_cmd_rectangle_toggle(struct window_copy_cmd_state *cs) struct window_copy_mode_data *data = wme->data; data->lineflag = LINE_SEL_NONE; - window_copy_rectangle_toggle(wme); + window_copy_rectangle_set(wme, !data->rectflag); return (WINDOW_COPY_CMD_NOTHING); } @@ -2251,6 +2275,10 @@ static const struct { window_copy_cmd_previous_space }, { "previous-word", 0, 0, WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, window_copy_cmd_previous_word }, + { "rectangle-on", 0, 0, WINDOW_COPY_CMD_CLEAR_ALWAYS, + window_copy_cmd_rectangle_on }, + { "rectangle-off", 0, 0, WINDOW_COPY_CMD_CLEAR_ALWAYS, + window_copy_cmd_rectangle_off }, { "rectangle-toggle", 0, 0, WINDOW_COPY_CMD_CLEAR_ALWAYS, window_copy_cmd_rectangle_toggle }, { "refresh-from-pane", 0, 0, WINDOW_COPY_CMD_CLEAR_ALWAYS, @@ -4726,12 +4754,12 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) } static void -window_copy_rectangle_toggle(struct window_mode_entry *wme) +window_copy_rectangle_set(struct window_mode_entry *wme, int rectflag) { struct window_copy_mode_data *data = wme->data; u_int px, py; - data->rectflag = !data->rectflag; + data->rectflag = rectflag; py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); From 9fcf413d877731bdd8ac75fe756a5f80f97aa52e Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 22 Jan 2021 11:28:33 +0000 Subject: [PATCH 48/90] Revert clear changes to writing as they don't work properly, better change to come. --- screen-write.c | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/screen-write.c b/screen-write.c index 7df5cd92..f374630c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1516,7 +1516,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, struct screen_write_collect_item *ci, *tmp; struct screen_write_collect_line *cl; u_int y, cx, cy, items = 0; - int clear = 0; struct tty_ctx ttyctx; size_t written = 0; @@ -1540,29 +1539,22 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, cx = s->cx; cy = s->cy; for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; + if (cl->bg != 0) { + screen_write_set_cursor(ctx, 0, y); + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.bg = cl->bg - 1; + tty_write(tty_cmd_clearline, &ttyctx); + } TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { - if (clear != -1 && - (u_int)clear != ci->x && - cl->bg != 0) { - screen_write_set_cursor(ctx, clear, y); - screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.bg = cl->bg - 1; - ttyctx.num = ci->x - clear; - log_debug("clear %u at %u", ttyctx.num, clear); - tty_write(tty_cmd_clearcharacter, &ttyctx); - } - screen_write_set_cursor(ctx, ci->x, y); if (ci->type == CLEAR_END) { screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; tty_write(tty_cmd_clearendofline, &ttyctx); - clear = -1; } else if (ci->type == CLEAR_START) { screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; tty_write(tty_cmd_clearstartofline, &ttyctx); - clear = ci->x + 1; } else { screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = &ci->gc; @@ -1570,7 +1562,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.ptr = cl->data + ci->x; ttyctx.num = ci->used; tty_write(tty_cmd_cells, &ttyctx); - clear = ci->x + ci->used; } items++; @@ -1579,16 +1570,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, TAILQ_REMOVE(&cl->items, ci, entry); free(ci); } - if (clear != -1 && - (u_int)clear != screen_size_x(s) - 1 && - cl->bg != 0) { - screen_write_set_cursor(ctx, clear, y); - screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.bg = cl->bg - 1; - log_debug("clear to end at %u", clear); - tty_write(tty_cmd_clearendofline, &ttyctx); - } - clear = 0; cl->bg = 0; } s->cx = cx; s->cy = cy; From d6542c333d92aeee74c0a70939e5976de015bb0c Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 26 Jan 2021 09:32:52 +0000 Subject: [PATCH 49/90] Always resize the original screen before copying when exiting the alternate screen, GitHub issue 2536. --- screen.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/screen.c b/screen.c index d39447de..2cdbec62 100644 --- a/screen.c +++ b/screen.c @@ -574,7 +574,14 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) void screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) { - u_int sx, sy; + u_int sx = screen_size_x(s), sy = screen_size_y(s); + + /* + * If the current size is different, temporarily resize to the old size + * before copying back. + */ + if (s->saved_grid != NULL) + screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 1); /* * Restore the cursor position and cell. This happens even if not @@ -582,29 +589,23 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) */ if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) { s->cx = s->saved_cx; - if (s->cx > screen_size_x(s) - 1) - s->cx = screen_size_x(s) - 1; s->cy = s->saved_cy; - if (s->cy > screen_size_y(s) - 1) - s->cy = screen_size_y(s) - 1; if (gc != NULL) memcpy(gc, &s->saved_cell, sizeof *gc); } - if (s->saved_grid == NULL) + /* If not in the alternate screen, do nothing more. */ + if (s->saved_grid == NULL) { + if (s->cx > screen_size_x(s) - 1) + s->cx = screen_size_x(s) - 1; + if (s->cy > screen_size_y(s) - 1) + s->cy = screen_size_y(s) - 1; return; - sx = screen_size_x(s); - sy = screen_size_y(s); - - /* - * If the current size is bigger, temporarily resize to the old size - * before copying back. - */ - if (sy > s->saved_grid->sy) - screen_resize(s, sx, s->saved_grid->sy, 1); + } /* Restore the saved grid. */ - grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0, sy); + grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0, + s->saved_grid->sy); /* * Turn history back on (so resize can use it) and then resize back to @@ -612,9 +613,13 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) */ if (s->saved_flags & GRID_HISTORY) s->grid->flags |= GRID_HISTORY; - if (sy > s->saved_grid->sy || sx != s->saved_grid->sx) - screen_resize(s, sx, sy, 1); + screen_resize(s, sx, sy, 1); grid_destroy(s->saved_grid); s->saved_grid = NULL; + + if (s->cx > screen_size_x(s) - 1) + s->cx = screen_size_x(s) - 1; + if (s->cy > screen_size_y(s) - 1) + s->cy = screen_size_y(s) - 1; } From 8156d9ba416c28d76b445e5c74537c254a3bc110 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 27 Jan 2021 10:42:52 +0000 Subject: [PATCH 50/90] Flush pending output before entering or exiting alternate screen rather than leaking it, oss-fuzz issue 29959. --- screen-write.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/screen-write.c b/screen-write.c index f374630c..4811b0a1 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1970,6 +1970,8 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) return; + + screen_write_collect_flush(ctx, 0, __func__); screen_alternate_on(ctx->s, gc, cursor); screen_write_initctx(ctx, &ttyctx, 1); @@ -1986,6 +1988,8 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) return; + + screen_write_collect_flush(ctx, 0, __func__); screen_alternate_off(ctx->s, gc, cursor); screen_write_initctx(ctx, &ttyctx, 1); From 255802d8d7357bf985bf2e4221eac8ab64b348ea Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 29 Jan 2021 09:48:43 +0000 Subject: [PATCH 51/90] Trim output overwritten by later text or clears completely rather than only in a few cases. This means we can better track when a line should wrap. GitHub issue 2537. --- format.c | 3 - screen-write.c | 369 +++++++++++++++++++++++++++---------------------- tmux.1 | 2 - tmux.h | 65 ++++----- 4 files changed, 230 insertions(+), 209 deletions(-) diff --git a/format.c b/format.c index df1898a6..25b80249 100644 --- a/format.c +++ b/format.c @@ -3038,9 +3038,6 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) format_add_cb(ft, "history_bytes", format_cb_history_bytes); format_add_cb(ft, "history_all_bytes", format_cb_history_all_bytes); - format_add(ft, "pane_written", "%zu", wp->written); - format_add(ft, "pane_skipped", "%zu", wp->skipped); - if (window_pane_index(wp, &idx) != 0) fatalx("index not found"); format_add(ft, "pane_index", "%u", idx); diff --git a/screen-write.c b/screen-write.c index 4811b0a1..8e9ec582 100644 --- a/screen-write.c +++ b/screen-write.c @@ -23,13 +23,11 @@ #include "tmux.h" +static struct screen_write_citem *screen_write_collect_trim( + struct screen_write_ctx *, u_int, u_int, u_int, int *); static void screen_write_collect_clear(struct screen_write_ctx *, u_int, u_int); -static void screen_write_collect_clear_end(struct screen_write_ctx *, u_int, - u_int); -static void screen_write_collect_clear_start(struct screen_write_ctx *, - u_int, u_int); -static void screen_write_collect_scroll(struct screen_write_ctx *); +static void screen_write_collect_scroll(struct screen_write_ctx *, u_int); static void screen_write_collect_flush(struct screen_write_ctx *, int, const char *); @@ -38,23 +36,44 @@ static int screen_write_overwrite(struct screen_write_ctx *, static const struct grid_cell *screen_write_combine(struct screen_write_ctx *, const struct utf8_data *, u_int *); -struct screen_write_collect_item { - u_int x; - int wrapped; +struct screen_write_citem { + u_int x; + int wrapped; - enum { TEXT, CLEAR_END, CLEAR_START } type; - u_int used; - u_int bg; + enum { TEXT, CLEAR } type; + u_int used; + u_int bg; - struct grid_cell gc; + struct grid_cell gc; - TAILQ_ENTRY(screen_write_collect_item) entry; + TAILQ_ENTRY(screen_write_citem) entry; }; -struct screen_write_collect_line { - u_int bg; - char *data; - TAILQ_HEAD(, screen_write_collect_item) items; +struct screen_write_cline { + char *data; + TAILQ_HEAD(, screen_write_citem) items; }; +TAILQ_HEAD(, screen_write_citem) screen_write_citem_freelist = + TAILQ_HEAD_INITIALIZER(screen_write_citem_freelist); + +static struct screen_write_citem * +screen_write_get_citem(void) +{ + struct screen_write_citem *ci; + + ci = TAILQ_FIRST(&screen_write_citem_freelist); + if (ci != NULL) { + TAILQ_REMOVE(&screen_write_citem_freelist, ci, entry); + memset(ci, 0, sizeof *ci); + return (ci); + } + return (xcalloc(1, sizeof *ci)); +} + +static void +screen_write_free_citem(struct screen_write_citem *ci) +{ + TAILQ_INSERT_TAIL(&screen_write_citem_freelist, ci, entry); +} static void screen_write_offset_timer(__unused int fd, __unused short events, void *data) @@ -125,7 +144,8 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) * Redraw is already deferred to redraw another pane - redraw * this one also when that happens. */ - log_debug("adding %%%u to deferred redraw", wp->id); + log_debug("%s: adding %%%u to deferred redraw", __func__, + wp->id); wp->flags |= PANE_REDRAW; return (-1); } @@ -220,7 +240,7 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s) if (ctx->s->write_list == NULL) screen_write_make_list(ctx->s); - ctx->item = xcalloc(1, sizeof *ctx->item); + ctx->item = screen_write_get_citem(); ctx->scrolled = 0; ctx->bg = 8; @@ -278,14 +298,7 @@ screen_write_stop(struct screen_write_ctx *ctx) screen_write_collect_end(ctx); screen_write_collect_flush(ctx, 0, __func__); - log_debug("%s: %u cells (%u written, %u skipped)", __func__, - ctx->cells, ctx->written, ctx->skipped); - if (ctx->wp != NULL) { - ctx->wp->written += ctx->written; - ctx->wp->skipped += ctx->skipped; - } - - free(ctx->item); + screen_write_free_citem(ctx->item); } /* Reset screen state. */ @@ -1094,9 +1107,10 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) void screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct grid_line *gl; - u_int sx = screen_size_x(s); + struct screen *s = ctx->s; + struct grid_line *gl; + u_int sx = screen_size_x(s); + struct screen_write_citem *ci = ctx->item; gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) @@ -1105,18 +1119,22 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); screen_write_collect_clear(ctx, s->cy, 1); - ctx->s->write_list[s->cy].bg = 1 + bg; - ctx->item->used = 0; + ci->x = 0; + ci->used = sx; + ci->type = CLEAR; + ci->bg = bg; + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); + ctx->item = screen_write_get_citem(); } /* Clear to end of line from cursor. */ void screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct grid_line *gl; - u_int sx = screen_size_x(s); - struct screen_write_collect_item *ci = ctx->item; + struct screen *s = ctx->s; + struct grid_line *gl; + u_int sx = screen_size_x(s); + struct screen_write_citem *ci = ctx->item, *before; if (s->cx == 0) { screen_write_clearline(ctx, bg); @@ -1129,12 +1147,16 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); - screen_write_collect_clear_end(ctx, s->cy, s->cx); + before = screen_write_collect_trim(ctx, s->cy, s->cx, sx - s->cx, NULL); ci->x = s->cx; - ci->type = CLEAR_END; + ci->used = sx - s->cx; + ci->type = CLEAR; ci->bg = bg; - TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); - ctx->item = xcalloc(1, sizeof *ctx->item); + if (before == NULL) + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); + else + TAILQ_INSERT_BEFORE(before, ci, entry); + ctx->item = screen_write_get_citem(); } /* Clear to start of line from cursor. */ @@ -1142,8 +1164,8 @@ void screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) { struct screen *s = ctx->s; - u_int sx = screen_size_x(s); - struct screen_write_collect_item *ci = ctx->item; + u_int sx = screen_size_x(s); + struct screen_write_citem *ci = ctx->item, *before; if (s->cx >= sx - 1) { screen_write_clearline(ctx, bg); @@ -1155,12 +1177,16 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) else grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1, bg); - screen_write_collect_clear_start(ctx, s->cy, s->cx); - ci->x = s->cx; - ci->type = CLEAR_START; + before = screen_write_collect_trim(ctx, s->cy, 0, s->cx + 1, NULL); + ci->x = 0; + ci->used = s->cx + 1; + ci->type = CLEAR; ci->bg = bg; - TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); - ctx->item = xcalloc(1, sizeof *ctx->item); + if (before == NULL) + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); + else + TAILQ_INSERT_BEFORE(before, ci, entry); + ctx->item = screen_write_get_citem(); } /* Move cursor to px,py. */ @@ -1182,6 +1208,7 @@ screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, if (py != -1 && (u_int)py > screen_size_y(s) - 1) py = screen_size_y(s) - 1; + log_debug("%s: from %u,%u to %u,%u", __func__, s->cx, s->cy, px, py); screen_write_set_cursor(ctx, px, py); } @@ -1250,7 +1277,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) if (s->cy == s->rlower) { grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); - screen_write_collect_scroll(ctx); + screen_write_collect_scroll(ctx, bg); ctx->scrolled++; } else if (s->cy < screen_size_y(s) - 1) screen_write_set_cursor(ctx, -1, s->cy + 1); @@ -1276,7 +1303,7 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) for (i = 0; i < lines; i++) { grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); - screen_write_collect_scroll(ctx); + screen_write_collect_scroll(ctx, bg); } ctx->scrolled += lines; } @@ -1390,107 +1417,114 @@ screen_write_clearhistory(struct screen_write_ctx *ctx) grid_clear_history(ctx->s->grid); } -/* Clear to start of a collected line. */ -static void -screen_write_collect_clear_start(struct screen_write_ctx *ctx, u_int y, u_int x) +/* Trim collected items. */ +static struct screen_write_citem * +screen_write_collect_trim(struct screen_write_ctx *ctx, u_int y, u_int x, + u_int used, int *wrapped) { - struct screen_write_collect_item *ci, *tmp; - size_t size = 0; - u_int items = 0; + struct screen_write_cline *cl = &ctx->s->write_list[y]; + struct screen_write_citem *ci, *ci2, *tmp, *before = NULL; + u_int sx = x, ex = x + used - 1; + u_int csx, cex; - if (TAILQ_EMPTY(&ctx->s->write_list[y].items)) - return; - TAILQ_FOREACH_SAFE(ci, &ctx->s->write_list[y].items, entry, tmp) { - switch (ci->type) { - case CLEAR_START: - break; - case CLEAR_END: - if (ci->x <= x) - ci->x = x; + if (TAILQ_EMPTY(&cl->items)) + return (NULL); + TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + csx = ci->x; + cex = ci->x + ci->used - 1; + + /* Item is entirely before. */ + if (cex < sx) { + log_debug("%s: %p %u-%u before %u-%u", __func__, ci, + csx, cex, sx, ex); continue; - case TEXT: - if (ci->x > x) - continue; + } + + /* Item is entirely after. */ + if (csx > ex) { + log_debug("%s: %p %u-%u after %u-%u", __func__, ci, + csx, cex, sx, ex); + before = ci; break; } - items++; - size += ci->used; - TAILQ_REMOVE(&ctx->s->write_list[y].items, ci, entry); - free(ci); - } - ctx->skipped += size; - log_debug("%s: dropped %u items (%zu bytes) (line %u)", __func__, items, - size, y); -} -/* Clear to end of a collected line. */ -static void -screen_write_collect_clear_end(struct screen_write_ctx *ctx, u_int y, u_int x) -{ - struct screen_write_collect_item *ci, *tmp; - size_t size = 0; - u_int items = 0; - - if (TAILQ_EMPTY(&ctx->s->write_list[y].items)) - return; - TAILQ_FOREACH_SAFE(ci, &ctx->s->write_list[y].items, entry, tmp) { - switch (ci->type) { - case CLEAR_START: - if (ci->x >= x) - ci->x = x; + /* Item is entirely inside. */ + if (csx >= sx && cex <= ex) { + log_debug("%s: %p %u-%u inside %u-%u", __func__, ci, + csx, cex, sx, ex); + TAILQ_REMOVE(&cl->items, ci, entry); + screen_write_free_citem(ci); + if (csx == 0 && ci->wrapped && wrapped != NULL) + *wrapped = 1; continue; - case CLEAR_END: - break; - case TEXT: - if (ci->x < x) - continue; + } + + /* Item under the start. */ + if (csx < sx && cex >= sx && cex <= ex) { + log_debug("%s: %p %u-%u start %u-%u", __func__, ci, + csx, cex, sx, ex); + ci->used = sx - csx; + log_debug("%s: %p now %u-%u", __func__, ci, ci->x, + ci->x + ci->used + 1); + continue; + } + + /* Item covers the end. */ + if (cex > ex && csx >= sx && csx <= ex) { + log_debug("%s: %p %u-%u end %u-%u", __func__, ci, + csx, cex, sx, ex); + ci->x = ex + 1; + ci->used = cex - ex; + log_debug("%s: %p now %u-%u", __func__, ci, ci->x, + ci->x + ci->used + 1); + before = ci; break; } - items++; - size += ci->used; - TAILQ_REMOVE(&ctx->s->write_list[y].items, ci, entry); - free(ci); + + /* Item must cover both sides. */ + log_debug("%s: %p %u-%u under %u-%u", __func__, ci, + csx, cex, sx, ex); + ci2 = screen_write_get_citem(); + ci2->type = ci->type; + ci2->bg = ci->bg; + memcpy(&ci2->gc, &ci->gc, sizeof ci2->gc); + TAILQ_INSERT_AFTER(&cl->items, ci, ci2, entry); + + ci->used = sx - csx; + ci2->x = ex + 1; + ci2->used = cex - ex; + + log_debug("%s: %p now %u-%u (%p) and %u-%u (%p)", __func__, ci, + ci->x, ci->x + ci->used - 1, ci, ci2->x, + ci2->x + ci2->used - 1, ci2); + before = ci2; + break; } - ctx->skipped += size; - log_debug("%s: dropped %u items (%zu bytes) (line %u)", __func__, items, - size, y); + return (before); } /* Clear collected lines. */ static void screen_write_collect_clear(struct screen_write_ctx *ctx, u_int y, u_int n) { - struct screen_write_collect_item *ci, *tmp; - struct screen_write_collect_line *cl; - u_int i, items; - size_t size; + struct screen_write_cline *cl; + u_int i; for (i = y; i < y + n; i++) { - if (TAILQ_EMPTY(&ctx->s->write_list[i].items)) - continue; - items = 0; - size = 0; cl = &ctx->s->write_list[i]; - TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { - items++; - size += ci->used; - TAILQ_REMOVE(&cl->items, ci, entry); - free(ci); - } - ctx->skipped += size; - log_debug("%s: dropped %u items (%zu bytes) (line %u)", - __func__, items, size, y); + TAILQ_CONCAT(&screen_write_citem_freelist, &cl->items, entry); } } /* Scroll collected lines up. */ static void -screen_write_collect_scroll(struct screen_write_ctx *ctx) +screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct screen_write_collect_line *cl; - u_int y; - char *saved; + struct screen *s = ctx->s; + struct screen_write_cline *cl; + u_int y; + char *saved; + struct screen_write_citem *ci; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, s->rupper, s->rlower); @@ -1500,11 +1534,16 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx) for (y = s->rupper; y < s->rlower; y++) { cl = &ctx->s->write_list[y + 1]; TAILQ_CONCAT(&ctx->s->write_list[y].items, &cl->items, entry); - ctx->s->write_list[y].bg = cl->bg; ctx->s->write_list[y].data = cl->data; } - ctx->s->write_list[s->rlower].bg = 1 + 8; ctx->s->write_list[s->rlower].data = saved; + + ci = screen_write_get_citem(); + ci->x = 0; + ci->used = screen_size_x(s); + ci->type = CLEAR; + ci->bg = bg; + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); } /* Flush collected lines. */ @@ -1512,12 +1551,11 @@ static void screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, const char *from) { - struct screen *s = ctx->s; - struct screen_write_collect_item *ci, *tmp; - struct screen_write_collect_line *cl; - u_int y, cx, cy, items = 0; - struct tty_ctx ttyctx; - size_t written = 0; + struct screen *s = ctx->s; + struct screen_write_citem *ci, *tmp; + struct screen_write_cline *cl; + u_int y, cx, cy, last, items = 0; + struct tty_ctx ttyctx; if (ctx->scrolled != 0) { log_debug("%s: scrolled %u (region %u-%u)", __func__, @@ -1539,22 +1577,18 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, cx = s->cx; cy = s->cy; for (y = 0; y < screen_size_y(s); y++) { cl = &ctx->s->write_list[y]; - if (cl->bg != 0) { - screen_write_set_cursor(ctx, 0, y); - screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.bg = cl->bg - 1; - tty_write(tty_cmd_clearline, &ttyctx); - } + last = UINT_MAX; TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + if (last != UINT_MAX && ci->x <= last) { + fatalx("collect list not in order: %u <= %u", + ci->x, last); + } screen_write_set_cursor(ctx, ci->x, y); - if (ci->type == CLEAR_END) { + if (ci->type == CLEAR) { screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = ci->bg; - tty_write(tty_cmd_clearendofline, &ttyctx); - } else if (ci->type == CLEAR_START) { - screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.bg = ci->bg; - tty_write(tty_cmd_clearstartofline, &ttyctx); + ttyctx.num = ci->used; + tty_write(tty_cmd_clearcharacter, &ttyctx); } else { screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = &ci->gc; @@ -1563,38 +1597,41 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, ttyctx.num = ci->used; tty_write(tty_cmd_cells, &ttyctx); } - items++; - written += ci->used; TAILQ_REMOVE(&cl->items, ci, entry); - free(ci); + screen_write_free_citem(ci); + last = ci->x; } - cl->bg = 0; } s->cx = cx; s->cy = cy; - log_debug("%s: flushed %u items (%zu bytes) (%s)", __func__, items, - written, from); - ctx->written += written; + log_debug("%s: flushed %u items (%s)", __func__, items, from); } /* Finish and store collected cells. */ void screen_write_collect_end(struct screen_write_ctx *ctx) { - struct screen *s = ctx->s; - struct screen_write_collect_item *ci = ctx->item; - struct screen_write_collect_line *cl = &s->write_list[s->cy]; - struct grid_cell gc; - u_int xx; + struct screen *s = ctx->s; + struct screen_write_citem *ci = ctx->item, *before; + struct screen_write_cline *cl = &s->write_list[s->cy]; + struct grid_cell gc; + u_int xx; + int wrapped = ci->wrapped; if (ci->used == 0) return; + before = screen_write_collect_trim(ctx, s->cy, s->cx, ci->used, + &wrapped); ci->x = s->cx; - TAILQ_INSERT_TAIL(&cl->items, ci, entry); - ctx->item = xcalloc(1, sizeof *ctx->item); + ci->wrapped = wrapped; + if (before == NULL) + TAILQ_INSERT_TAIL(&cl->items, ci, entry); + else + TAILQ_INSERT_BEFORE(before, ci, entry); + ctx->item = screen_write_get_citem(); log_debug("%s: %u %.*s (at %u,%u)", __func__, ci->used, (int)ci->used, cl->data + ci->x, s->cx, s->cy); @@ -1630,10 +1667,10 @@ void screen_write_collect_add(struct screen_write_ctx *ctx, const struct grid_cell *gc) { - struct screen *s = ctx->s; - struct screen_write_collect_item *ci; - u_int sx = screen_size_x(s); - int collect; + struct screen *s = ctx->s; + struct screen_write_citem *ci; + u_int sx = screen_size_x(s); + int collect; /* * Don't need to check that the attributes and whatnot are still the @@ -1658,7 +1695,6 @@ screen_write_collect_add(struct screen_write_ctx *ctx, screen_write_cell(ctx, gc); return; } - ctx->cells++; if (s->cx > sx - 1 || ctx->item->used > sx - 1 - s->cx) screen_write_collect_end(ctx); @@ -1695,7 +1731,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) return; - ctx->cells++; /* If the width is zero, combine onto the previous character. */ if (width == 0) { @@ -1822,9 +1857,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } else ttyctx.cell = gc; tty_write(tty_cmd_cell, &ttyctx); - ctx->written++; - } else - ctx->skipped++; + } } /* Combine a UTF-8 zero-width character onto the previous. */ diff --git a/tmux.1 b/tmux.1 index 160e6a92..247f0fb6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4823,7 +4823,6 @@ The following variables are available, where appropriate: .It Li "pane_pipe" Ta "" Ta "1 if pane is being piped" .It Li "pane_right" Ta "" Ta "Right of pane" .It Li "pane_search_string" Ta "" Ta "Last search string in copy mode" -.It Li "pane_skipped" Ta "" Ta "Bytes skipped as not visible in pane" .It Li "pane_start_command" Ta "" Ta "Command pane started with" .It Li "pane_synchronized" Ta "" Ta "1 if pane is synchronized" .It Li "pane_tabs" Ta "" Ta "Pane tab positions" @@ -4831,7 +4830,6 @@ The following variables are available, where appropriate: .It Li "pane_top" Ta "" Ta "Top of pane" .It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane" .It Li "pane_width" Ta "" Ta "Width of pane" -.It Li "pane_written" Ta "" Ta "Bytes written by pane (aside from redrawing)" .It Li "pid" Ta "" Ta "Server PID" .It Li "popup_key" Ta "" Ta "Key pressed in popup" .It Li "popup_mouse_x" Ta "" Ta "Mouse X position in popup" diff --git a/tmux.h b/tmux.h index c593a175..ce45cece 100644 --- a/tmux.h +++ b/tmux.h @@ -55,8 +55,8 @@ struct mouse_event; struct options; struct options_array_item; struct options_entry; -struct screen_write_collect_item; -struct screen_write_collect_line; +struct screen_write_citem; +struct screen_write_cline; struct screen_write_ctx; struct session; struct tty_ctx; @@ -794,55 +794,51 @@ struct style { struct screen_sel; struct screen_titles; struct screen { - char *title; - char *path; - struct screen_titles *titles; + char *title; + char *path; + struct screen_titles *titles; - struct grid *grid; /* grid data */ + struct grid *grid; /* grid data */ - u_int cx; /* cursor x */ - u_int cy; /* cursor y */ + u_int cx; /* cursor x */ + u_int cy; /* cursor y */ - u_int cstyle; /* cursor style */ - char *ccolour; /* cursor colour string */ + u_int cstyle; /* cursor style */ + char *ccolour; /* cursor colour string */ - u_int rupper; /* scroll region top */ - u_int rlower; /* scroll region bottom */ + u_int rupper; /* scroll region top */ + u_int rlower; /* scroll region bottom */ - int mode; + int mode; - u_int saved_cx; - u_int saved_cy; - struct grid *saved_grid; - struct grid_cell saved_cell; - int saved_flags; + u_int saved_cx; + u_int saved_cy; + struct grid *saved_grid; + struct grid_cell saved_cell; + int saved_flags; - bitstr_t *tabs; - struct screen_sel *sel; + bitstr_t *tabs; + struct screen_sel *sel; - struct screen_write_collect_line *write_list; + struct screen_write_cline *write_list; }; /* Screen write context. */ typedef void (*screen_write_init_ctx_cb)(struct screen_write_ctx *, struct tty_ctx *); struct screen_write_ctx { - struct window_pane *wp; - struct screen *s; + struct window_pane *wp; + struct screen *s; - int flags; + int flags; #define SCREEN_WRITE_SYNC 0x1 - screen_write_init_ctx_cb init_ctx_cb; - void *arg; + screen_write_init_ctx_cb init_ctx_cb; + void *arg; - struct screen_write_collect_item *item; - u_int scrolled; - u_int bg; - - u_int cells; - u_int written; - u_int skipped; + struct screen_write_citem *item; + u_int scrolled; + u_int bg; }; /* Screen redraw context. */ @@ -1001,9 +997,6 @@ struct window_pane { char *searchstr; int searchregex; - size_t written; - size_t skipped; - int border_gc_set; struct grid_cell border_gc; From 509221520c87510016f5c90aeea0d4dcc4b74a98 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Feb 2021 08:01:14 +0000 Subject: [PATCH 52/90] Add a no-detached choice to detach-on-destroy which detaches only if there are no other detached sessions to switch to, from Sencer Selcuk in GitHub issue 2553. --- options-table.c | 6 +++++- server-fn.c | 25 +++++++++++++++++++++---- tmux.1 | 6 +++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/options-table.c b/options-table.c index 74a59134..9fc69db5 100644 --- a/options-table.c +++ b/options-table.c @@ -72,6 +72,9 @@ static const char *options_table_window_size_list[] = { static const char *options_table_remain_on_exit_list[] = { "off", "on", "failed", NULL }; +static const char *options_table_detach_on_destroy_list[] = { + "off", "on", "no-detached", NULL +}; /* Status line format. */ #define OPTIONS_TABLE_STATUS_FORMAT1 \ @@ -405,8 +408,9 @@ const struct options_table_entry options_table[] = { }, { .name = "detach-on-destroy", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, + .choices = options_table_detach_on_destroy_list, .default_num = 1, .text = "Whether to detach when a session is destroyed, or switch " "the client to another session if any exist." diff --git a/server-fn.c b/server-fn.c index d0e06c0f..4358fa5c 100644 --- a/server-fn.c +++ b/server-fn.c @@ -401,9 +401,8 @@ server_destroy_session_group(struct session *s) static struct session * server_next_session(struct session *s) { - struct session *s_loop, *s_out; + struct session *s_loop, *s_out = NULL; - s_out = NULL; RB_FOREACH(s_loop, sessions, &sessions) { if (s_loop == s) continue; @@ -414,17 +413,35 @@ server_next_session(struct session *s) return (s_out); } +static struct session * +server_next_detached_session(struct session *s) +{ + struct session *s_loop, *s_out = NULL; + + RB_FOREACH(s_loop, sessions, &sessions) { + if (s_loop == s || s_loop->attached) + continue; + if (s_out == NULL || + timercmp(&s_loop->activity_time, &s_out->activity_time, <)) + s_out = s_loop; + } + return (s_out); +} + void server_destroy_session(struct session *s) { struct client *c; struct session *s_new; + int detach_on_destroy; - if (!options_get_number(s->options, "detach-on-destroy")) + detach_on_destroy = options_get_number(s->options, "detach-on-destroy"); + if (detach_on_destroy == 0) s_new = server_next_session(s); + else if (detach_on_destroy == 2) + s_new = server_next_detached_session(s); else s_new = NULL; - TAILQ_FOREACH(c, &clients, entry) { if (c->session != s) continue; diff --git a/tmux.1 b/tmux.1 index 247f0fb6..12d555e5 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3575,12 +3575,16 @@ The default is 80x24. If enabled and the session is no longer attached to any clients, it is destroyed. .It Xo Ic detach-on-destroy -.Op Ic on | off +.Op Ic off | on | no-detached .Xc If on (the default), the client is detached when the session it is attached to is destroyed. If off, the client is switched to the most recently active of the remaining sessions. +If +.Ic no-detached , +the client is detached only if there are no detached sessions; if detached +sessions exist, the client is switched to the most recently active. .It Ic display-panes-active-colour Ar colour Set the colour used by the .Ic display-panes From 5c48086e5c8e4ea9633fd6b913d33dcc2b47f2d7 Mon Sep 17 00:00:00 2001 From: jmc Date: Tue, 2 Feb 2021 07:33:29 +0000 Subject: [PATCH 53/90] article fixes; from eddie youseph --- tmux.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index 12d555e5..c076f0cb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1362,7 +1362,7 @@ a pane ID such as .Ql %0 ; .Ql %* for all panes in the attached session; -an window ID such as +a window ID such as .Ql @0 ; or .Ql @* From f0546b0ff816d1ca8199fc726f06639535cf526e Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 2 Feb 2021 13:03:03 +0000 Subject: [PATCH 54/90] Fix popup mouse position. --- popup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/popup.c b/popup.c index 97acebfd..675bb2f9 100644 --- a/popup.c +++ b/popup.c @@ -323,8 +323,8 @@ popup_key_cb(struct client *c, struct key_event *event) return (0); if (KEYC_IS_MOUSE(event->key)) { /* Must be inside, checked already. */ - if (!input_key_get_mouse(&pd->s, m, m->x - pd->px, - m->y - pd->py, &buf, &len)) + if (!input_key_get_mouse(&pd->s, m, m->x - pd->px - 1, + m->y - pd->py - 1, &buf, &len)) return (0); bufferevent_write(job_get_event(pd->job), buf, len); return (0); From c13f2e1135df1f8be78262eb6f5ccb251a7e1d61 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 4 Feb 2021 14:02:24 +0000 Subject: [PATCH 55/90] Redraw status line and borders on pane enable/disable, GitHub issue 2554. --- cmd-select-pane.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 30529722..fa388548 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -108,11 +108,15 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "no last pane"); return (CMD_RETURN_ERROR); } - if (args_has(args, 'e')) + if (args_has(args, 'e')) { lastwp->flags &= ~PANE_INPUTOFF; - else if (args_has(args, 'd')) + server_redraw_window_borders(lastwp->window); + server_status_window(lastwp->window); + } else if (args_has(args, 'd')) { lastwp->flags |= PANE_INPUTOFF; - else { + server_redraw_window_borders(lastwp->window); + server_status_window(lastwp->window); + } else { if (window_push_zoom(w, args_has(args, 'Z'))) server_redraw_window(w); window_redraw_active_switch(w, lastwp); @@ -188,10 +192,14 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'e')) { wp->flags &= ~PANE_INPUTOFF; + server_redraw_window_borders(wp->window); + server_status_window(wp->window); return (CMD_RETURN_NORMAL); } if (args_has(args, 'd')) { wp->flags |= PANE_INPUTOFF; + server_redraw_window_borders(wp->window); + server_status_window(wp->window); return (CMD_RETURN_NORMAL); } From e3d71d9bdfa31fb658794759f07af43d53253e5f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 5 Feb 2021 11:00:45 +0000 Subject: [PATCH 56/90] Add compat clock_gettime for older macOS. GitHub issue 2555. --- compat.h | 12 ++++++++++++ compat/clock_gettime.c | 37 +++++++++++++++++++++++++++++++++++++ configure.ac | 1 + 3 files changed, 50 insertions(+) create mode 100644 compat/clock_gettime.c diff --git a/compat.h b/compat.h index 828c956d..13334ad7 100644 --- a/compat.h +++ b/compat.h @@ -265,6 +265,13 @@ void warnx(const char *, ...); #define HOST_NAME_MAX 255 #endif +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + #ifndef HAVE_FLOCK #define LOCK_SH 0 #define LOCK_EX 0 @@ -342,6 +349,11 @@ const char *getprogname(void); void setproctitle(const char *, ...); #endif +#ifndef HAVE_CLOCK_GETTIME +/* clock_gettime.c */ +int clock_gettime(int, struct timespec *); +#endif + #ifndef HAVE_B64_NTOP /* base64.c */ #undef b64_ntop diff --git a/compat/clock_gettime.c b/compat/clock_gettime.c new file mode 100644 index 00000000..8290e75c --- /dev/null +++ b/compat/clock_gettime.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "compat.h" + +#ifndef TIMEVAL_TO_TIMESPEC +#define TIMEVAL_TO_TIMESPEC(tv, ts) do { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ +} while (0) +#endif + +int +clock_gettime(int clock, struct timespec *ts) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + TIMEVAL_TO_TIMESPEC(&tv, ts); + return 0; +} diff --git a/configure.ac b/configure.ac index 269dd5aa..4175f5c8 100644 --- a/configure.ac +++ b/configure.ac @@ -133,6 +133,7 @@ AC_CHECK_FUNCS([ \ AC_REPLACE_FUNCS([ \ asprintf \ cfmakeraw \ + clock_gettime \ closefrom \ explicit_bzero \ fgetln \ From be471c328ea0ae04026e4ff32fda7b7f11c74255 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 5 Feb 2021 12:23:49 +0000 Subject: [PATCH 57/90] Add a -S flag to new-window to make it select the existing window if one with the given name already exists rather than failing with an error. Also add a format to check if a window or session name exists which allows the same with other commands. Requested by and discussed with kn@. --- cmd-new-window.c | 38 ++++++++++++++++++++++++++---- format.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++- tmux.1 | 21 ++++++++++++++++- 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/cmd-new-window.c b/cmd-new-window.c index ca3e66c4..712e2a79 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -38,8 +38,8 @@ const struct cmd_entry cmd_new_window_entry = { .name = "new-window", .alias = "neww", - .args = { "abc:de:F:kn:Pt:", 0, -1 }, - .usage = "[-abdkP] [-c start-directory] [-e environment] [-F format] " + .args = { "abc:de:F:kn:PSt:", 0, -1 }, + .usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] " "[-n window-name] " CMD_TARGET_WINDOW_USAGE " [command]", .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX }, @@ -52,6 +52,7 @@ static enum cmd_retval cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); struct cmd_find_state *current = cmdq_get_current(item); struct cmd_find_state *target = cmdq_get_target(item); struct spawn_context sc; @@ -59,12 +60,41 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) struct session *s = target->s; struct winlink *wl = target->wl; int idx = target->idx, before; - struct winlink *new_wl; + struct winlink *new_wl = NULL; char *cause = NULL, *cp; - const char *template, *add; + const char *template, *add, *name; struct cmd_find_state fs; struct args_value *value; + /* + * If -S and -n are given and -t is not and a single window with this + * name already exists, select it. + */ + name = args_get(args, 'n'); + if (args_has(args, 'S') && name != NULL && target->idx == -1) { + RB_FOREACH(wl, winlinks, &s->windows) { + if (strcmp(wl->window->name, name) != 0) + continue; + if (new_wl == NULL) { + new_wl = wl; + continue; + } + cmdq_error(item, "multiple windows named %s", name); + return (CMD_RETURN_ERROR); + } + if (new_wl != NULL) { + if (args_has(args, 'd')) + return (CMD_RETURN_NORMAL); + if (session_set_current(s, new_wl) == 0) + server_redraw_session(s); + if (c != NULL && c->session != NULL) + s->curw->window->latest = c; + recalculate_sizes(); + return (CMD_RETURN_NORMAL); + } + } + + before = args_has(args, 'b'); if (args_has(args, 'a') || before) { idx = winlink_shuffle_up(s, wl, before); diff --git a/format.c b/format.c index 25b80249..ecf299d1 100644 --- a/format.c +++ b/format.c @@ -100,6 +100,8 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_LENGTH 0x800 #define FORMAT_WIDTH 0x1000 #define FORMAT_QUOTE_STYLE 0x2000 +#define FORMAT_WINDOW_NAME 0x4000 +#define FORMAT_SESSION_NAME 0x8000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 10 @@ -1733,7 +1735,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, } /* Now try single character with arguments. */ - if (strchr("mCst=peq", cp[0]) == NULL) + if (strchr("mCNst=peq", cp[0]) == NULL) break; c = cp[0]; @@ -1857,6 +1859,24 @@ format_search(struct format_modifier *fm, struct window_pane *wp, const char *s) return (value); } +/* Does session name exist? */ +static char * +format_session_name(struct format_expand_state *es, const char *fmt) +{ + char *name; + struct session *s; + + name = format_expand1(es, fmt); + RB_FOREACH(s, sessions, &sessions) { + if (strcmp(s->name, name) == 0) { + free(name); + return (xstrdup("1")); + } + } + free(name); + return (xstrdup("0")); +} + /* Loop over sessions. */ static char * format_loop_sessions(struct format_expand_state *es, const char *fmt) @@ -1892,6 +1912,30 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt) return (value); } +/* Does window name exist? */ +static char * +format_window_name(struct format_expand_state *es, const char *fmt) +{ + struct format_tree *ft = es->ft; + char *name; + struct winlink *wl; + + if (ft->s == NULL) { + format_log(es, "window name but no session"); + return (NULL); + } + + name = format_expand1(es, fmt); + RB_FOREACH(wl, winlinks, &ft->s->windows) { + if (strcmp(wl->window->name, name) == 0) { + free(name); + return (xstrdup("1")); + } + } + free(name); + return (xstrdup("0")); +} + /* Loop over windows. */ static char * format_loop_windows(struct format_expand_state *es, const char *fmt) @@ -2251,6 +2295,13 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, case 'T': modifiers |= FORMAT_EXPANDTIME; break; + case 'N': + if (fm->argc < 1 || + strchr(fm->argv[0], 'w') != NULL) + modifiers |= FORMAT_WINDOW_NAME; + else if (strchr(fm->argv[0], 's') != NULL) + modifiers |= FORMAT_SESSION_NAME; + break; case 'S': modifiers |= FORMAT_SESSIONS; break; @@ -2291,6 +2342,14 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, value = format_loop_panes(es, copy); if (value == NULL) goto fail; + } else if (modifiers & FORMAT_WINDOW_NAME) { + value = format_window_name(es, copy); + if (value == NULL) + goto fail; + } else if (modifiers & FORMAT_SESSION_NAME) { + value = format_session_name(es, copy); + if (value == NULL) + goto fail; } else if (search != NULL) { /* Search in pane. */ new = format_expand1(es, copy); diff --git a/tmux.1 b/tmux.1 index c076f0cb..530cd559 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2348,7 +2348,7 @@ the .Ic base-index option. .It Xo Ic new-window -.Op Fl abdkP +.Op Fl abdkPS .Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl F Ar format @@ -2377,6 +2377,14 @@ represents the window to be created; if the target already exists an error is shown, unless the .Fl k flag is used, in which case it is destroyed. +If +.Fl S +is given and a window named +.Ar window-name +already exists, it is selected (unless +.Fl d +is also given in which case the command does nothing). +.Pp .Ar shell-command is the command to execute. If @@ -4688,6 +4696,17 @@ For example, to get a list of windows formatted like the status line: #{W:#{E:window-status-format} ,#{E:window-status-current-format} } .Ed .Pp +.Ql N:\& +checks if a window (without any suffix or with the +.Ql w +suffix) or a session (with the +.Ql s +suffix) name exists, for example +.Ql `N/w:foo` +is replaced with 1 if a window named +.Ql foo +exists. +.Pp A prefix of the form .Ql s/foo/bar/:\& will substitute From 3dddc11603b1bd66b5586b47795716e87921a096 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 5 Feb 2021 12:29:18 +0000 Subject: [PATCH 58/90] Send Unicode directional isolate characters around horizontal pane borders if the terminal support UTF-8 and an extension terminfo(5) capability "Bidi" is present. On terminals with BiDi support (ie, VTE) this seems to be enough to display right-to-left text acceptably enough to be usable (with some caveats about the mouse position). Requested by and with help from Mahmoud Elagdar in GitHub issue 2425. --- grid.c | 4 ++-- screen-redraw.c | 24 +++++++++++++++++++----- tmux.1 | 4 ++++ tmux.h | 1 + tty-term.c | 1 + 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/grid.c b/grid.c index cce304c9..7744587a 100644 --- a/grid.c +++ b/grid.c @@ -1045,14 +1045,14 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, srcl->cellsize * sizeof *dstl->celldata); } else dstl->celldata = NULL; - if (srcl->extdsize != 0) { dstl->extdsize = srcl->extdsize; dstl->extddata = xreallocarray(NULL, dstl->extdsize, sizeof *dstl->extddata); memcpy(dstl->extddata, srcl->extddata, dstl->extdsize * sizeof *dstl->extddata); - } + } else + dstl->extddata = NULL; sy++; dy++; diff --git a/screen-redraw.c b/screen-redraw.c index 47c1a838..6ddabc52 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -32,8 +32,8 @@ static void screen_redraw_set_context(struct client *, struct screen_redraw_ctx *); #define CELL_INSIDE 0 -#define CELL_LEFTRIGHT 1 -#define CELL_TOPBOTTOM 2 +#define CELL_TOPBOTTOM 1 +#define CELL_LEFTRIGHT 2 #define CELL_TOPLEFT 3 #define CELL_TOPRIGHT 4 #define CELL_BOTTOMLEFT 5 @@ -47,6 +47,9 @@ static void screen_redraw_set_context(struct client *, #define CELL_BORDERS " xqlkmjwvtun~" +#define START_ISOLATE "\342\201\246" +#define END_ISOLATE "\342\201\251" + static const struct utf8_data screen_redraw_double_borders[] = { { "", 0, 0, 0 }, { "\342\225\221", 0, 3, 1 }, /* U+2551 */ @@ -299,7 +302,7 @@ screen_redraw_type_of_cell(struct client *c, u_int px, u_int py, case 13: /* 1101, left right bottom */ return (CELL_TOPJOIN); case 12: /* 1100, left right */ - return (CELL_TOPBOTTOM); + return (CELL_LEFTRIGHT); case 11: /* 1011, left top bottom */ return (CELL_RIGHTJOIN); case 10: /* 1010, left top */ @@ -313,7 +316,7 @@ screen_redraw_type_of_cell(struct client *c, u_int px, u_int py, case 5: /* 0101, right bottom */ return (CELL_TOPLEFT); case 3: /* 0011, top bottom */ - return (CELL_LEFTRIGHT); + return (CELL_TOPBOTTOM); } return (CELL_OUTSIDE); } @@ -680,7 +683,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) struct tty *tty = &c->tty; struct window_pane *wp; u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; - int pane_status = ctx->pane_status; + int pane_status = ctx->pane_status, isolates; struct grid_cell gc; const struct grid_cell *tmp; @@ -705,11 +708,22 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) } screen_redraw_border_set(wp, ctx->pane_lines, cell_type, &gc); + if (cell_type == CELL_TOPBOTTOM && + (c->flags & CLIENT_UTF8) && + tty_term_has(tty->term, TTYC_BIDI)) + isolates = 1; + else + isolates = 0; + if (ctx->statustop) tty_cursor(tty, i, ctx->statuslines + j); else tty_cursor(tty, i, j); + if (isolates) + tty_puts(tty, END_ISOLATE); tty_cell(tty, &gc, &grid_default_cell, NULL); + if (isolates) + tty_puts(tty, START_ISOLATE); } /* Draw the borders. */ diff --git a/tmux.1 b/tmux.1 index 530cd559..090b9436 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5953,6 +5953,10 @@ option should be used. An existing extension that tells .Nm the terminal supports default colours. +.It Em \&Bidi +Tell +.Nm +that the terminal supports the VTE bidirectional text extensions. .It Em \&Cs , Cr Set the cursor colour. The first takes a single string argument and is used to set the colour; diff --git a/tmux.h b/tmux.h index ce45cece..1cca8c8f 100644 --- a/tmux.h +++ b/tmux.h @@ -261,6 +261,7 @@ enum tty_code_code { TTYC_AX, TTYC_BCE, TTYC_BEL, + TTYC_BIDI, TTYC_BLINK, TTYC_BOLD, TTYC_CIVIS, diff --git a/tty-term.c b/tty-term.c index b4feea60..b989328c 100644 --- a/tty-term.c +++ b/tty-term.c @@ -58,6 +58,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_AX] = { TTYCODE_FLAG, "AX" }, [TTYC_BCE] = { TTYCODE_FLAG, "bce" }, [TTYC_BEL] = { TTYCODE_STRING, "bel" }, + [TTYC_BIDI] = { TTYCODE_STRING, "Bidi" }, [TTYC_BLINK] = { TTYCODE_STRING, "blink" }, [TTYC_BOLD] = { TTYCODE_STRING, "bold" }, [TTYC_CIVIS] = { TTYCODE_STRING, "civis" }, From 1e29ebd41289543897b73354e97c654641a4e079 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 6 Feb 2021 13:02:52 +0000 Subject: [PATCH 59/90] In the end UTF-8 did not become a terminal feature, should not be listed in man page. --- tmux.1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/tmux.1 b/tmux.1 index 090b9436..85b3ee45 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3458,8 +3458,6 @@ Supports title setting. .It usstyle Allows underscore style and colour to be set. -.It UTF-8 -Is able to handle UTF-8 output. .El .It Ic terminal-overrides[] Ar string Allow terminal descriptions read using From c579be1f2a8205c6405f7c6fdb229b31f30274c8 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Feb 2021 08:33:54 +0000 Subject: [PATCH 60/90] Include "focused" in client flags, from Dan Aloni in GitHub issue 2558. --- server-client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server-client.c b/server-client.c index a55188d1..52df8110 100644 --- a/server-client.c +++ b/server-client.c @@ -2454,6 +2454,8 @@ server_client_get_flags(struct client *c) *s = '\0'; if (c->flags & CLIENT_ATTACHED) strlcat(s, "attached,", sizeof s); + if (c->flags & CLIENT_FOCUSED) + strlcat(s, "focused,", sizeof s); if (c->flags & CLIENT_CONTROL) strlcat(s, "control-mode,", sizeof s); if (c->flags & CLIENT_IGNORESIZE) From e3005e5ec4385d284abdeb3cecafc69c14655649 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Feb 2021 14:46:53 +0000 Subject: [PATCH 61/90] Add "pipe" variants of the "copy-pipe" commands which do not copy, from Christian Zangl. --- tmux.1 | 7 ++++- window-copy.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/tmux.1 b/tmux.1 index 85b3ee45..775d0e28 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1653,6 +1653,9 @@ The following commands are supported in copy mode: .It Li "page-down" Ta "C-f" Ta "PageDown" .It Li "page-down-and-cancel" Ta "" Ta "" .It Li "page-up" Ta "C-b" Ta "PageUp" +.It Li "pipe [] []" Ta "" Ta "" +.It Li "pipe-no-clear [] []" Ta "" Ta "" +.It Li "pipe-and-cancel [] []" Ta "" Ta "" .It Li "previous-matching-bracket" Ta "" Ta "M-C-b" .It Li "previous-paragraph" Ta "{" Ta "M-{" .It Li "previous-space" Ta "B" Ta "" @@ -1708,7 +1711,9 @@ so buffers are named .Ql buffer1 and so on). Pipe commands take a command argument which is the command to which the -copied text is piped. +selected text is piped. +.Ql copy-pipe +variants also copy the selection. The .Ql -and-cancel variants of some commands exit copy mode after they have completed (for copy diff --git a/window-copy.c b/window-copy.c index 89b2a9af..c6b25413 100644 --- a/window-copy.c +++ b/window-copy.c @@ -92,6 +92,8 @@ static void window_copy_synchronize_cursor(struct window_mode_entry *, int); static void *window_copy_get_selection(struct window_mode_entry *, size_t *); static void window_copy_copy_buffer(struct window_mode_entry *, const char *, void *, size_t); +static void window_copy_pipe(struct window_mode_entry *, + struct session *, const char *); static void window_copy_copy_pipe(struct window_mode_entry *, struct session *, const char *, const char *); static void window_copy_copy_selection(struct window_mode_entry *, @@ -1874,6 +1876,44 @@ window_copy_cmd_copy_pipe_and_cancel(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); } +static enum window_copy_cmd_action +window_copy_cmd_pipe_no_clear(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + char *command = NULL; + + if (s != NULL && cs->args->argc > 1 && *cs->args->argv[1] != '\0') + command = format_single(NULL, cs->args->argv[1], c, s, wl, wp); + window_copy_pipe(wme, s, command); + free(command); + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_pipe(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_pipe_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_pipe_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_pipe_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_CANCEL); +} + static enum window_copy_cmd_action window_copy_cmd_goto_line(struct window_copy_cmd_state *cs) { @@ -2267,6 +2307,12 @@ static const struct { window_copy_cmd_page_down_and_cancel }, { "page-up", 0, 0, WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, window_copy_cmd_page_up }, + { "pipe-no-clear", 0, 1, WINDOW_COPY_CMD_CLEAR_NEVER, + window_copy_cmd_pipe_no_clear }, + { "pipe", 0, 1, WINDOW_COPY_CMD_CLEAR_ALWAYS, + window_copy_cmd_pipe }, + { "pipe-and-cancel", 0, 1, WINDOW_COPY_CMD_CLEAR_ALWAYS, + window_copy_cmd_pipe_and_cancel }, { "previous-matching-bracket", 0, 0, WINDOW_COPY_CMD_CLEAR_ALWAYS, window_copy_cmd_previous_matching_bracket }, { "previous-paragraph", 0, 0, WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, @@ -3840,22 +3886,41 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, paste_add(prefix, buf, len); } -static void -window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s, - const char *prefix, const char *cmd) +static void * +window_copy_pipe_run(struct window_mode_entry *wme, struct session *s, + const char *cmd, size_t *len) { void *buf; - size_t len; struct job *job; - buf = window_copy_get_selection(wme, &len); + buf = window_copy_get_selection(wme, len); if (cmd == NULL || *cmd == '\0') cmd = options_get_string(global_options, "copy-command"); if (cmd != NULL && *cmd != '\0') { job = job_run(cmd, s, NULL, NULL, NULL, NULL, NULL, JOB_NOWAIT, -1, -1); - bufferevent_write(job_get_event(job), buf, len); + bufferevent_write(job_get_event(job), buf, *len); } + return (buf); +} + +static void +window_copy_pipe(struct window_mode_entry *wme, struct session *s, + const char *cmd) +{ + size_t len; + + window_copy_pipe_run(wme, s, cmd, &len); +} + +static void +window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s, + const char *prefix, const char *cmd) +{ + void *buf; + size_t len; + + buf = window_copy_pipe_run(wme, s, cmd, &len); if (buf != NULL) window_copy_copy_buffer(wme, prefix, buf, len); } From 1492ae11a5c4f29e783f1d49f3580ee7f4d276e4 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 9 Feb 2021 14:25:40 +0000 Subject: [PATCH 62/90] Do not expand times and #() inside #(). --- format.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index ecf299d1..0e773498 100644 --- a/format.c +++ b/format.c @@ -367,7 +367,10 @@ format_job_get(struct format_expand_state *es, const char *cmd) RB_INSERT(format_job_tree, jobs, fj); } - expanded = format_expand1(es, cmd); + format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS); + next.flags &= ~FORMAT_EXPAND_TIME; + + expanded = format_expand1(&next, cmd); if (fj->expanded == NULL || strcmp(expanded, fj->expanded) != 0) { free((void *)fj->expanded); fj->expanded = xstrdup(expanded); @@ -393,7 +396,6 @@ format_job_get(struct format_expand_state *es, const char *cmd) if (ft->flags & FORMAT_STATUS) fj->status = 1; - format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS); return (format_expand1(&next, fj->out)); } From 32186950f5ae3e3517f39b2723cc247a9da93d8c Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Feb 2021 07:17:07 +0000 Subject: [PATCH 63/90] Use ~/.tmux.conf as an example rather than /etc/passwd, suggested by deraadt@. --- tmux.1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tmux.1 b/tmux.1 index 775d0e28..e1891009 100644 --- a/tmux.1 +++ b/tmux.1 @@ -872,12 +872,12 @@ arguments are commands. This may be a single argument passed to the shell, for example: .Bd -literal -offset indent -new-window 'vi /etc/passwd' +new-window 'vi ~/.tmux.conf' .Ed .Pp Will run: .Bd -literal -offset indent -/bin/sh -c 'vi /etc/passwd' +/bin/sh -c 'vi ~/.tmux.conf' .Ed .Pp Additionally, the @@ -894,7 +894,7 @@ to be given as multiple arguments and executed directly (without This can avoid issues with shell quoting. For example: .Bd -literal -offset indent -$ tmux new-window vi /etc/passwd +$ tmux new-window vi ~/.tmux.conf .Ed .Pp Will run @@ -940,7 +940,7 @@ $ tmux kill-window -t :1 $ tmux new-window \e; split-window -d -$ tmux new-session -d 'vi /etc/passwd' \e; split-window -d \e; attach +$ tmux new-session -d 'vi ~/.tmux.conf' \e; split-window -d \e; attach .Ed .Sh CLIENTS AND SESSIONS The From 679b2288e81a0aa1c375a5ae16c23866114ed766 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 10 Feb 2021 17:18:37 +0000 Subject: [PATCH 64/90] Restore utf8proc bits that went missing, GitHub issue 2564. --- utf8.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/utf8.c b/utf8.c index 458363b8..f43945e6 100644 --- a/utf8.c +++ b/utf8.c @@ -216,7 +216,11 @@ utf8_width(struct utf8_data *ud, int *width) { wchar_t wc; +#ifdef HAVE_UTF8PROC + switch (utf8proc_mbtowc(&wc, ud->data, ud->size)) { +#else switch (mbtowc(&wc, ud->data, ud->size)) { +#endif case -1: log_debug("UTF-8 %.*s, mbtowc() %d", (int)ud->size, ud->data, errno); @@ -225,7 +229,11 @@ utf8_width(struct utf8_data *ud, int *width) case 0: return (UTF8_ERROR); } +#ifdef HAVE_UTF8PROC + *width = utf8proc_wcwidth(wc); +#else *width = wcwidth(wc); +#endif if (*width >= 0 && *width <= 0xff) return (UTF8_DONE); log_debug("UTF-8 %.*s, wcwidth() %d", (int)ud->size, ud->data, *width); From e40831a0023705885643284b574913e08a59f496 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 11 Feb 2021 08:28:45 +0000 Subject: [PATCH 65/90] Move file handling protocol stuff all into file.c so it can be reused more easily. --- client.c | 273 ++---------------------------- file.c | 434 +++++++++++++++++++++++++++++++++++++++++++++--- server-client.c | 77 +-------- tmux.h | 17 +- 4 files changed, 445 insertions(+), 356 deletions(-) diff --git a/client.c b/client.c index a3e300cd..e164e930 100644 --- a/client.c +++ b/client.c @@ -477,257 +477,6 @@ client_send_identify(const char *ttynam, const char *cwd, int feat) proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0); } -/* File write error callback. */ -static void -client_write_error_callback(__unused struct bufferevent *bev, - __unused short what, void *arg) -{ - struct client_file *cf = arg; - - log_debug("write error file %d", cf->stream); - - bufferevent_free(cf->event); - cf->event = NULL; - - close(cf->fd); - cf->fd = -1; - - if (client_exitflag) - client_exit(); -} - -/* File write callback. */ -static void -client_write_callback(__unused struct bufferevent *bev, void *arg) -{ - struct client_file *cf = arg; - - if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) { - bufferevent_free(cf->event); - close(cf->fd); - RB_REMOVE(client_files, &client_files, cf); - file_free(cf); - } - - if (client_exitflag) - client_exit(); -} - -/* Open write file. */ -static void -client_write_open(void *data, size_t datalen) -{ - struct msg_write_open *msg = data; - const char *path; - struct msg_write_ready reply; - struct client_file find, *cf; - const int flags = O_NONBLOCK|O_WRONLY|O_CREAT; - int error = 0; - - if (datalen < sizeof *msg) - fatalx("bad MSG_WRITE_OPEN size"); - if (datalen == sizeof *msg) - path = "-"; - else - path = (const char *)(msg + 1); - log_debug("open write file %d %s", msg->stream, path); - - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { - cf = file_create(NULL, msg->stream, NULL, NULL); - RB_INSERT(client_files, &client_files, cf); - } else { - error = EBADF; - goto reply; - } - if (cf->closed) { - error = EBADF; - goto reply; - } - - cf->fd = -1; - if (msg->fd == -1) - cf->fd = open(path, msg->flags|flags, 0644); - else { - if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO) - errno = EBADF; - else { - cf->fd = dup(msg->fd); - if (~client_flags & CLIENT_CONTROL) - close(msg->fd); /* can only be used once */ - } - } - if (cf->fd == -1) { - error = errno; - goto reply; - } - - cf->event = bufferevent_new(cf->fd, NULL, client_write_callback, - client_write_error_callback, cf); - bufferevent_enable(cf->event, EV_WRITE); - goto reply; - -reply: - reply.stream = msg->stream; - reply.error = error; - proc_send(client_peer, MSG_WRITE_READY, -1, &reply, sizeof reply); -} - -/* Write to client file. */ -static void -client_write_data(void *data, size_t datalen) -{ - struct msg_write_data *msg = data; - struct client_file find, *cf; - size_t size = datalen - sizeof *msg; - - if (datalen < sizeof *msg) - fatalx("bad MSG_WRITE size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) - fatalx("unknown stream number"); - log_debug("write %zu to file %d", size, cf->stream); - - if (cf->event != NULL) - bufferevent_write(cf->event, msg + 1, size); -} - -/* Close client file. */ -static void -client_write_close(void *data, size_t datalen) -{ - struct msg_write_close *msg = data; - struct client_file find, *cf; - - if (datalen != sizeof *msg) - fatalx("bad MSG_WRITE_CLOSE size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) - fatalx("unknown stream number"); - log_debug("close file %d", cf->stream); - - if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) { - if (cf->event != NULL) - bufferevent_free(cf->event); - if (cf->fd != -1) - close(cf->fd); - RB_REMOVE(client_files, &client_files, cf); - file_free(cf); - } -} - -/* File read callback. */ -static void -client_read_callback(__unused struct bufferevent *bev, void *arg) -{ - struct client_file *cf = arg; - void *bdata; - size_t bsize; - struct msg_read_data *msg; - size_t msglen; - - msg = xmalloc(sizeof *msg); - for (;;) { - bdata = EVBUFFER_DATA(cf->event->input); - bsize = EVBUFFER_LENGTH(cf->event->input); - - if (bsize == 0) - break; - if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg) - bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg; - log_debug("read %zu from file %d", bsize, cf->stream); - - msglen = (sizeof *msg) + bsize; - msg = xrealloc(msg, msglen); - msg->stream = cf->stream; - memcpy(msg + 1, bdata, bsize); - proc_send(client_peer, MSG_READ, -1, msg, msglen); - - evbuffer_drain(cf->event->input, bsize); - } - free(msg); -} - -/* File read error callback. */ -static void -client_read_error_callback(__unused struct bufferevent *bev, - __unused short what, void *arg) -{ - struct client_file *cf = arg; - struct msg_read_done msg; - - log_debug("read error file %d", cf->stream); - - msg.stream = cf->stream; - msg.error = 0; - proc_send(client_peer, MSG_READ_DONE, -1, &msg, sizeof msg); - - bufferevent_free(cf->event); - close(cf->fd); - RB_REMOVE(client_files, &client_files, cf); - file_free(cf); -} - -/* Open read file. */ -static void -client_read_open(void *data, size_t datalen) -{ - struct msg_read_open *msg = data; - const char *path; - struct msg_read_done reply; - struct client_file find, *cf; - const int flags = O_NONBLOCK|O_RDONLY; - int error; - - if (datalen < sizeof *msg) - fatalx("bad MSG_READ_OPEN size"); - if (datalen == sizeof *msg) - path = "-"; - else - path = (const char *)(msg + 1); - log_debug("open read file %d %s", msg->stream, path); - - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { - cf = file_create(NULL, msg->stream, NULL, NULL); - RB_INSERT(client_files, &client_files, cf); - } else { - error = EBADF; - goto reply; - } - if (cf->closed) { - error = EBADF; - goto reply; - } - - cf->fd = -1; - if (msg->fd == -1) - cf->fd = open(path, flags); - else { - if (msg->fd != STDIN_FILENO) - errno = EBADF; - else { - cf->fd = dup(msg->fd); - if (~client_flags & CLIENT_CONTROL) - close(msg->fd); /* can only be used once */ - } - } - if (cf->fd == -1) { - error = errno; - goto reply; - } - - cf->event = bufferevent_new(cf->fd, client_read_callback, NULL, - client_read_error_callback, cf); - bufferevent_enable(cf->event, EV_READ); - return; - -reply: - reply.stream = msg->stream; - reply.error = error; - proc_send(client_peer, MSG_READ_DONE, -1, &reply, sizeof reply); -} - /* Run command in shell; used for -c. */ static __dead void client_exec(const char *shell, const char *shellcmd) @@ -802,6 +551,16 @@ client_signal(int sig) } } +/* Callback for file write error or close. */ +static void +client_file_check_cb(__unused struct client *c, __unused const char *path, + __unused int error, __unused int closed, __unused struct evbuffer *buffer, + __unused void *data) +{ + if (client_exitflag) + client_exit(); +} + /* Callback for client read events. */ static void client_dispatch(struct imsg *imsg, __unused void *arg) @@ -916,16 +675,20 @@ client_dispatch_wait(struct imsg *imsg) proc_exit(client_proc); break; case MSG_READ_OPEN: - client_read_open(data, datalen); + file_read_open(&client_files, client_peer, imsg, 1, + !(client_flags & CLIENT_CONTROL), client_file_check_cb, + NULL); break; case MSG_WRITE_OPEN: - client_write_open(data, datalen); + file_write_open(&client_files, client_peer, imsg, 1, + !(client_flags & CLIENT_CONTROL), client_file_check_cb, + NULL); break; case MSG_WRITE: - client_write_data(data, datalen); + file_write_data(&client_files, imsg); break; case MSG_WRITE_CLOSE: - client_write_close(data, datalen); + file_write_close(&client_files, imsg); break; case MSG_OLDSTDERR: case MSG_OLDSTDIN: diff --git a/file.c b/file.c index 0d25db03..495568fa 100644 --- a/file.c +++ b/file.c @@ -30,10 +30,17 @@ #include "tmux.h" +/* + * IPC file handling. Both client and server use the same data structures + * (client_file and client_files) to store list of active files. Most functions + * are for use either in client or server but not both. + */ + static int file_next_stream = 3; RB_GENERATE(client_files, client_file, entry, file_cmp); +/* Get path for file, either as given or from working directory. */ static char * file_get_path(struct client *c, const char *file) { @@ -46,6 +53,7 @@ file_get_path(struct client *c, const char *file) return (path); } +/* Tree comparison function. */ int file_cmp(struct client_file *cf1, struct client_file *cf2) { @@ -56,11 +64,47 @@ file_cmp(struct client_file *cf1, struct client_file *cf2) return (0); } +/* + * Create a file object in the client process - the peer is the server to send + * messages to. Check callback is fired when the file is finished with so the + * process can decide if it needs to exit (if it is waiting for files to + * flush). + */ struct client_file * -file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) +file_create_with_peer(struct tmuxpeer *peer, struct client_files *files, + int stream, client_file_cb cb, void *cbdata) { struct client_file *cf; + cf = xcalloc(1, sizeof *cf); + cf->c = NULL; + cf->references = 1; + cf->stream = stream; + + cf->buffer = evbuffer_new(); + if (cf->buffer == NULL) + fatalx("out of memory"); + + cf->cb = cb; + cf->data = cbdata; + + cf->peer = peer; + cf->tree = files; + RB_INSERT(client_files, files, cf); + + return (cf); +} + +/* Create a file object in the server, communicating with the given client. */ +struct client_file * +file_create_with_client(struct client *c, int stream, client_file_cb cb, + void *cbdata) +{ + struct client_file *cf; + + if (c != NULL && (c->flags & CLIENT_ATTACHED)) + c = NULL; + cf = xcalloc(1, sizeof *cf); cf->c = c; cf->references = 1; @@ -74,6 +118,8 @@ file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) cf->data = cbdata; if (cf->c != NULL) { + cf->peer = cf->c->peer; + cf->tree = &cf->c->files; RB_INSERT(client_files, &cf->c->files, cf); cf->c->references++; } @@ -81,6 +127,7 @@ file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) return (cf); } +/* Free a file. */ void file_free(struct client_file *cf) { @@ -90,13 +137,15 @@ file_free(struct client_file *cf) evbuffer_free(cf->buffer); free(cf->path); - if (cf->c != NULL) { - RB_REMOVE(client_files, &cf->c->files, cf); + if (cf->tree != NULL) + RB_REMOVE(client_files, cf->tree, cf); + if (cf->c != NULL) server_client_unref(cf->c); - } + free(cf); } +/* Event to fire the done callback. */ static void file_fire_done_cb(__unused int fd, __unused short events, void *arg) { @@ -108,21 +157,22 @@ file_fire_done_cb(__unused int fd, __unused short events, void *arg) file_free(cf); } +/* Add an event to fire the done callback (used by the server). */ void file_fire_done(struct client_file *cf) { event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL); } +/* Fire the read callback. */ void file_fire_read(struct client_file *cf) { - struct client *c = cf->c; - if (cf->cb != NULL) - cf->cb(c, cf->path, cf->error, 0, cf->buffer, cf->data); + cf->cb(cf->c, cf->path, cf->error, 0, cf->buffer, cf->data); } +/* Can this file be printed to? */ int file_can_print(struct client *c) { @@ -133,6 +183,7 @@ file_can_print(struct client *c) return (1); } +/* Print a message to a file. */ void file_print(struct client *c, const char *fmt, ...) { @@ -143,6 +194,7 @@ file_print(struct client *c, const char *fmt, ...) va_end(ap); } +/* Print a message to a file. */ void file_vprint(struct client *c, const char *fmt, va_list ap) { @@ -154,7 +206,7 @@ file_vprint(struct client *c, const char *fmt, va_list ap) find.stream = 1; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { - cf = file_create(c, 1, NULL, NULL); + cf = file_create_with_client(c, 1, NULL, NULL); cf->path = xstrdup("-"); evbuffer_add_vprintf(cf->buffer, fmt, ap); @@ -169,6 +221,7 @@ file_vprint(struct client *c, const char *fmt, va_list ap) } } +/* Print a buffer to a file. */ void file_print_buffer(struct client *c, void *data, size_t size) { @@ -180,7 +233,7 @@ file_print_buffer(struct client *c, void *data, size_t size) find.stream = 1; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { - cf = file_create(c, 1, NULL, NULL); + cf = file_create_with_client(c, 1, NULL, NULL); cf->path = xstrdup("-"); evbuffer_add(cf->buffer, data, size); @@ -195,6 +248,7 @@ file_print_buffer(struct client *c, void *data, size_t size) } } +/* Report an error to a file. */ void file_error(struct client *c, const char *fmt, ...) { @@ -209,7 +263,7 @@ file_error(struct client *c, const char *fmt, ...) find.stream = 2; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { - cf = file_create(c, 2, NULL, NULL); + cf = file_create_with_client(c, 2, NULL, NULL); cf->path = xstrdup("-"); evbuffer_add_vprintf(cf->buffer, fmt, ap); @@ -226,19 +280,21 @@ file_error(struct client *c, const char *fmt, ...) va_end(ap); } +/* Write data to a file. */ void file_write(struct client *c, const char *path, int flags, const void *bdata, size_t bsize, client_file_cb cb, void *cbdata) { struct client_file *cf; - FILE *f; struct msg_write_open *msg; size_t msglen; int fd = -1; + u_int stream = file_next_stream++; + FILE *f; const char *mode; if (strcmp(path, "-") == 0) { - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = xstrdup("-"); fd = STDOUT_FILENO; @@ -251,7 +307,7 @@ file_write(struct client *c, const char *path, int flags, const void *bdata, goto skip; } - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = file_get_path(c, path); if (c == NULL || c->flags & CLIENT_ATTACHED) { @@ -286,7 +342,7 @@ skip: msg->fd = fd; msg->flags = flags; memcpy(msg + 1, cf->path, msglen - sizeof *msg); - if (proc_send(c->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) { + if (proc_send(cf->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) { free(msg); cf->error = EINVAL; goto done; @@ -298,18 +354,21 @@ done: file_fire_done(cf); } +/* Read a file. */ void file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) { struct client_file *cf; - FILE *f; struct msg_read_open *msg; - size_t msglen, size; + size_t msglen; int fd = -1; + u_int stream = file_next_stream++; + FILE *f; + size_t size; char buffer[BUFSIZ]; if (strcmp(path, "-") == 0) { - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = xstrdup("-"); fd = STDIN_FILENO; @@ -322,7 +381,7 @@ file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) goto skip; } - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = file_get_path(c, path); if (c == NULL || c->flags & CLIENT_ATTACHED) { @@ -358,7 +417,7 @@ skip: msg->stream = cf->stream; msg->fd = fd; memcpy(msg + 1, cf->path, msglen - sizeof *msg); - if (proc_send(c->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) { + if (proc_send(cf->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) { free(msg); cf->error = EINVAL; goto done; @@ -370,21 +429,21 @@ done: file_fire_done(cf); } +/* Push event, fired if there is more writing to be done. */ static void file_push_cb(__unused int fd, __unused short events, void *arg) { struct client_file *cf = arg; - struct client *c = cf->c; - if (~c->flags & CLIENT_DEAD) + if (cf->c == NULL || ~cf->c->flags & CLIENT_DEAD) file_push(cf); file_free(cf); } +/* Push uwritten data to the client for a file, if it will accept it. */ void file_push(struct client_file *cf) { - struct client *c = cf->c; struct msg_write_data *msg; size_t msglen, sent, left; struct msg_write_close close; @@ -400,21 +459,344 @@ file_push(struct client_file *cf) msg = xrealloc(msg, msglen); msg->stream = cf->stream; memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent); - if (proc_send(c->peer, MSG_WRITE, -1, msg, msglen) != 0) + if (proc_send(cf->peer, MSG_WRITE, -1, msg, msglen) != 0) break; evbuffer_drain(cf->buffer, sent); left = EVBUFFER_LENGTH(cf->buffer); - log_debug("%s: file %d sent %zu, left %zu", c->name, cf->stream, - sent, left); + log_debug("file %d sent %zu, left %zu", cf->stream, sent, left); } if (left != 0) { cf->references++; event_once(-1, EV_TIMEOUT, file_push_cb, cf, NULL); } else if (cf->stream > 2) { close.stream = cf->stream; - proc_send(c->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); + proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); file_fire_done(cf); } free(msg); } + +/* Client file write error callback. */ +static void +file_write_error_callback(__unused struct bufferevent *bev, __unused short what, + void *arg) +{ + struct client_file *cf = arg; + + log_debug("write error file %d", cf->stream); + + if (cf->cb != NULL) + cf->cb(NULL, NULL, 0, -1, NULL, cf->data); + + bufferevent_free(cf->event); + cf->event = NULL; + + close(cf->fd); + cf->fd = -1; +} + +/* Client file write callback. */ +static void +file_write_callback(__unused struct bufferevent *bev, void *arg) +{ + struct client_file *cf = arg; + + log_debug("write check file %d", cf->stream); + + if (cf->cb != NULL) + cf->cb(NULL, NULL, 0, -1, NULL, cf->data); + + if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) { + bufferevent_free(cf->event); + close(cf->fd); + RB_REMOVE(client_files, cf->tree, cf); + file_free(cf); + } +} + +/* Handle a file write open message (client). */ +void +file_write_open(struct client_files *files, struct tmuxpeer *peer, + struct imsg *imsg, int allow_streams, int close_received, + client_file_cb cb, void *cbdata) +{ + struct msg_write_open *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + const char *path; + struct msg_write_ready reply; + struct client_file find, *cf; + const int flags = O_NONBLOCK|O_WRONLY|O_CREAT; + int error = 0; + + if (msglen < sizeof *msg) + fatalx("bad MSG_WRITE_OPEN size"); + if (msglen == sizeof *msg) + path = "-"; + else + path = (const char *)(msg + 1); + log_debug("open write file %d %s", msg->stream, path); + + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) != NULL) { + error = EBADF; + goto reply; + } + cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata); + if (cf->closed) { + error = EBADF; + goto reply; + } + + cf->fd = -1; + if (msg->fd == -1) + cf->fd = open(path, msg->flags|flags, 0644); + else if (allow_streams) { + if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO) + errno = EBADF; + else { + cf->fd = dup(msg->fd); + if (close_received) + close(msg->fd); /* can only be used once */ + } + } else + errno = EBADF; + if (cf->fd == -1) { + error = errno; + goto reply; + } + + cf->event = bufferevent_new(cf->fd, NULL, file_write_callback, + file_write_error_callback, cf); + bufferevent_enable(cf->event, EV_WRITE); + goto reply; + +reply: + reply.stream = msg->stream; + reply.error = error; + proc_send(peer, MSG_WRITE_READY, -1, &reply, sizeof reply); +} + +/* Handle a file write data message (client). */ +void +file_write_data(struct client_files *files, struct imsg *imsg) +{ + struct msg_write_data *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + size_t size = msglen - sizeof *msg; + + if (msglen < sizeof *msg) + fatalx("bad MSG_WRITE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("write %zu to file %d", size, cf->stream); + + if (cf->event != NULL) + bufferevent_write(cf->event, msg + 1, size); +} + +/* Handle a file write close message (client). */ +void +file_write_close(struct client_files *files, struct imsg *imsg) +{ + struct msg_write_close *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_WRITE_CLOSE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("close file %d", cf->stream); + + if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) { + if (cf->event != NULL) + bufferevent_free(cf->event); + if (cf->fd != -1) + close(cf->fd); + RB_REMOVE(client_files, files, cf); + file_free(cf); + } +} + +/* Client file read error callback. */ +static void +file_read_error_callback(__unused struct bufferevent *bev, __unused short what, + void *arg) +{ + struct client_file *cf = arg; + struct msg_read_done msg; + + log_debug("read error file %d", cf->stream); + + msg.stream = cf->stream; + msg.error = 0; + proc_send(cf->peer, MSG_READ_DONE, -1, &msg, sizeof msg); + + bufferevent_free(cf->event); + close(cf->fd); + RB_REMOVE(client_files, cf->tree, cf); + file_free(cf); +} + +/* Client file read callback. */ +static void +file_read_callback(__unused struct bufferevent *bev, void *arg) +{ + struct client_file *cf = arg; + void *bdata; + size_t bsize; + struct msg_read_data *msg; + size_t msglen; + + msg = xmalloc(sizeof *msg); + for (;;) { + bdata = EVBUFFER_DATA(cf->event->input); + bsize = EVBUFFER_LENGTH(cf->event->input); + + if (bsize == 0) + break; + if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg) + bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg; + log_debug("read %zu from file %d", bsize, cf->stream); + + msglen = (sizeof *msg) + bsize; + msg = xrealloc(msg, msglen); + msg->stream = cf->stream; + memcpy(msg + 1, bdata, bsize); + proc_send(cf->peer, MSG_READ, -1, msg, msglen); + + evbuffer_drain(cf->event->input, bsize); + } + free(msg); +} + +/* Handle a file read open message (client). */ +void +file_read_open(struct client_files *files, struct tmuxpeer *peer, + struct imsg *imsg, int allow_streams, int close_received, client_file_cb cb, + void *cbdata) +{ + struct msg_read_open *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + const char *path; + struct msg_read_done reply; + struct client_file find, *cf; + const int flags = O_NONBLOCK|O_RDONLY; + int error; + + if (msglen < sizeof *msg) + fatalx("bad MSG_READ_OPEN size"); + if (msglen == sizeof *msg) + path = "-"; + else + path = (const char *)(msg + 1); + log_debug("open read file %d %s", msg->stream, path); + + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) != NULL) { + error = EBADF; + goto reply; + } + cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata); + if (cf->closed) { + error = EBADF; + goto reply; + } + + cf->fd = -1; + if (msg->fd == -1) + cf->fd = open(path, flags); + else if (allow_streams) { + if (msg->fd != STDIN_FILENO) + errno = EBADF; + else { + cf->fd = dup(msg->fd); + if (close_received) + close(msg->fd); /* can only be used once */ + } + } else + errno = EBADF; + if (cf->fd == -1) { + error = errno; + goto reply; + } + + cf->event = bufferevent_new(cf->fd, file_read_callback, NULL, + file_read_error_callback, cf); + bufferevent_enable(cf->event, EV_READ); + return; + +reply: + reply.stream = msg->stream; + reply.error = error; + proc_send(peer, MSG_READ_DONE, -1, &reply, sizeof reply); +} + +/* Handle a write ready message (server). */ +void +file_write_ready(struct client_files *files, struct imsg *imsg) +{ + struct msg_write_ready *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_WRITE_READY size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + return; + if (msg->error != 0) { + cf->error = msg->error; + file_fire_done(cf); + } else + file_push(cf); +} + +/* Handle read data message (server). */ +void +file_read_data(struct client_files *files, struct imsg *imsg) +{ + struct msg_read_data *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + void *bdata = msg + 1; + size_t bsize = msglen - sizeof *msg; + + if (msglen < sizeof *msg) + fatalx("bad MSG_READ_DATA size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + return; + + log_debug("file %d read %zu bytes", cf->stream, bsize); + if (cf->error == 0) { + if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { + cf->error = ENOMEM; + file_fire_done(cf); + } else + file_fire_read(cf); + } +} + +/* Handle a read done message (server). */ +void +file_read_done(struct client_files *files, struct imsg *imsg) +{ + struct msg_read_done *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_READ_DONE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + return; + + log_debug("file %d read done", cf->stream); + cf->error = msg->error; + file_fire_done(cf); +} diff --git a/server-client.c b/server-client.c index 52df8110..7d9cc0e6 100644 --- a/server-client.c +++ b/server-client.c @@ -51,12 +51,6 @@ static void server_client_dispatch(struct imsg *, void *); static void server_client_dispatch_command(struct client *, struct imsg *); static void server_client_dispatch_identify(struct client *, struct imsg *); static void server_client_dispatch_shell(struct client *); -static void server_client_dispatch_write_ready(struct client *, - struct imsg *); -static void server_client_dispatch_read_data(struct client *, - struct imsg *); -static void server_client_dispatch_read_done(struct client *, - struct imsg *); /* Compare client windows. */ static int @@ -2070,13 +2064,13 @@ server_client_dispatch(struct imsg *imsg, void *arg) server_client_dispatch_shell(c); break; case MSG_WRITE_READY: - server_client_dispatch_write_ready(c, imsg); + file_write_ready(&c->files, imsg); break; case MSG_READ: - server_client_dispatch_read_data(c, imsg); + file_read_data(&c->files, imsg); break; case MSG_READ_DONE: - server_client_dispatch_read_done(c, imsg); + file_read_done(&c->files, imsg); break; } } @@ -2302,71 +2296,6 @@ server_client_dispatch_shell(struct client *c) proc_kill_peer(c->peer); } -/* Handle write ready message. */ -static void -server_client_dispatch_write_ready(struct client *c, struct imsg *imsg) -{ - struct msg_write_ready *msg = imsg->data; - size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; - struct client_file find, *cf; - - if (msglen != sizeof *msg) - fatalx("bad MSG_WRITE_READY size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) - return; - if (msg->error != 0) { - cf->error = msg->error; - file_fire_done(cf); - } else - file_push(cf); -} - -/* Handle read data message. */ -static void -server_client_dispatch_read_data(struct client *c, struct imsg *imsg) -{ - struct msg_read_data *msg = imsg->data; - size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; - struct client_file find, *cf; - void *bdata = msg + 1; - size_t bsize = msglen - sizeof *msg; - - if (msglen < sizeof *msg) - fatalx("bad MSG_READ_DATA size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) - return; - - log_debug("%s: file %d read %zu bytes", c->name, cf->stream, bsize); - if (cf->error == 0) { - if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { - cf->error = ENOMEM; - file_fire_done(cf); - } else - file_fire_read(cf); - } -} - -/* Handle read done message. */ -static void -server_client_dispatch_read_done(struct client *c, struct imsg *imsg) -{ - struct msg_read_done *msg = imsg->data; - size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; - struct client_file find, *cf; - - if (msglen != sizeof *msg) - fatalx("bad MSG_READ_DONE size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) - return; - - log_debug("%s: file %d read done", c->name, cf->stream); - cf->error = msg->error; - file_fire_done(cf); -} - /* Get client working directory. */ const char * server_client_get_cwd(struct client *c, struct session *s) diff --git a/tmux.h b/tmux.h index 1cca8c8f..985b8858 100644 --- a/tmux.h +++ b/tmux.h @@ -1539,6 +1539,8 @@ typedef void (*client_file_cb) (struct client *, const char *, int, int, struct evbuffer *, void *); struct client_file { struct client *c; + struct tmuxpeer *peer; + struct client_files *tree; int references; int stream; @@ -1907,6 +1909,7 @@ int load_cfg(const char *, struct client *, struct cmdq_item *, int, int load_cfg_from_buffer(const void *, size_t, const char *, struct client *, struct cmdq_item *, int, struct cmdq_item **); void set_cfg_file(const char *); +const char *get_cfg_file(void); void printflike(1, 2) cfg_add_cause(const char *, ...); void cfg_print_causes(struct cmdq_item *); void cfg_show_causes(struct session *); @@ -2370,7 +2373,10 @@ void alerts_check_session(struct session *); /* file.c */ int file_cmp(struct client_file *, struct client_file *); RB_PROTOTYPE(client_files, client_file, entry, file_cmp); -struct client_file *file_create(struct client *, int, client_file_cb, void *); +struct client_file *file_create_with_peer(struct tmuxpeer *, + struct client_files *, int, client_file_cb, void *); +struct client_file *file_create_with_client(struct client *, int, + client_file_cb, void *); void file_free(struct client_file *); void file_fire_done(struct client_file *); void file_fire_read(struct client_file *); @@ -2383,6 +2389,15 @@ void file_write(struct client *, const char *, int, const void *, size_t, client_file_cb, void *); void file_read(struct client *, const char *, client_file_cb, void *); void file_push(struct client_file *); +void file_write_open(struct client_files *, struct tmuxpeer *, + struct imsg *, int, int, client_file_cb, void *); +void file_write_data(struct client_files *, struct imsg *); +void file_write_close(struct client_files *, struct imsg *); +void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *, + int, int, client_file_cb, void *); +void file_write_ready(struct client_files *, struct imsg *); +void file_read_data(struct client_files *, struct imsg *); +void file_read_done(struct client_files *, struct imsg *); /* server.c */ extern struct tmuxproc *server_proc; From 79e1984962281d94b25ff14ac3de31bc63358ead Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 11 Feb 2021 09:03:38 +0000 Subject: [PATCH 66/90] O_TRUNC is needed in case file exists. --- cmd-save-buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 5e45e279..7f161a91 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -109,7 +109,7 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'a')) flags = O_APPEND; else - flags = 0; + flags = O_TRUNC; file_write(cmdq_get_client(item), path, flags, bufdata, bufsize, cmd_save_buffer_done, item); free(path); From 2b58c226db055eff4bbb971fa00938b42690f4ac Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 11 Feb 2021 09:39:29 +0000 Subject: [PATCH 67/90] Add a couple of helper functions, and flush imsgs on exit. --- client.c | 15 +-------------- file.c | 20 ++++++++++++++++++++ proc.c | 36 ++++++++++++++++++++++++++++++++++++ server.c | 29 ++++++++--------------------- tmux.h | 2 ++ 5 files changed, 67 insertions(+), 35 deletions(-) diff --git a/client.c b/client.c index e164e930..74dc4602 100644 --- a/client.c +++ b/client.c @@ -223,20 +223,7 @@ client_exit_message(void) static void client_exit(void) { - struct client_file *cf; - size_t left; - int waiting = 0; - - RB_FOREACH (cf, client_files, &client_files) { - if (cf->event == NULL) - continue; - left = EVBUFFER_LENGTH(cf->event->output); - if (left != 0) { - waiting++; - log_debug("file %u %zu bytes left", cf->stream, left); - } - } - if (waiting == 0) + if (!file_write_left(&client_files)) proc_exit(client_proc); } diff --git a/file.c b/file.c index 495568fa..8eb4e178 100644 --- a/file.c +++ b/file.c @@ -477,6 +477,26 @@ file_push(struct client_file *cf) free(msg); } +/* Check if any files have data left to write. */ +int +file_write_left(struct client_files *files) +{ + struct client_file *cf; + size_t left; + int waiting = 0; + + RB_FOREACH (cf, client_files, files) { + if (cf->event == NULL) + continue; + left = EVBUFFER_LENGTH(cf->event->output); + if (left != 0) { + waiting++; + log_debug("file %u %zu bytes left", cf->stream, left); + } + } + return (waiting != 0); +} + /* Client file write error callback. */ static void file_write_error_callback(__unused struct bufferevent *bev, __unused short what, diff --git a/proc.c b/proc.c index ff011565..9412cec0 100644 --- a/proc.c +++ b/proc.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -45,6 +46,8 @@ struct tmuxproc { struct event ev_sigusr1; struct event ev_sigusr2; struct event ev_sigwinch; + + TAILQ_HEAD(, tmuxpeer) peers; }; struct tmuxpeer { @@ -58,6 +61,8 @@ struct tmuxpeer { void (*dispatchcb)(struct imsg *, void *); void *arg; + + TAILQ_ENTRY(tmuxpeer) entry; }; static int peer_check_version(struct tmuxpeer *, struct imsg *); @@ -190,6 +195,7 @@ proc_start(const char *name) tp = xcalloc(1, sizeof *tp); tp->name = xstrdup(name); + TAILQ_INIT(&tp->peers); return (tp); } @@ -207,6 +213,10 @@ proc_loop(struct tmuxproc *tp, int (*loopcb)(void)) void proc_exit(struct tmuxproc *tp) { + struct tmuxpeer *peer; + + TAILQ_FOREACH(peer, &tp->peers, entry) + imsg_flush(&peer->ibuf); tp->exit = 1; } @@ -297,6 +307,7 @@ proc_add_peer(struct tmuxproc *tp, int fd, event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); log_debug("add peer %p: %d (%p)", peer, fd, arg); + TAILQ_INSERT_TAIL(&tp->peers, peer, entry); proc_update_event(peer); return (peer); @@ -305,6 +316,7 @@ proc_add_peer(struct tmuxproc *tp, int fd, void proc_remove_peer(struct tmuxpeer *peer) { + TAILQ_REMOVE(&peer->parent->peers, peer, entry); log_debug("remove peer %p", peer); event_del(&peer->event); @@ -325,3 +337,27 @@ proc_toggle_log(struct tmuxproc *tp) { log_toggle(tp->name); } + +pid_t +proc_fork_and_daemon(int *fd) +{ + pid_t pid; + int pair[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) + fatal("socketpair failed"); + switch (pid = fork()) { + case -1: + fatal("fork failed"); + case 0: + close(pair[0]); + *fd = pair[1]; + if (daemon(1, 0) != 0) + fatal("daemon failed"); + return (0); + default: + close(pair[1]); + *fd = pair[0]; + return (pid); + } +} diff --git a/server.c b/server.c index b2c75e11..a286e779 100644 --- a/server.c +++ b/server.c @@ -156,35 +156,22 @@ int server_start(struct tmuxproc *client, int flags, struct event_base *base, int lockfd, char *lockfile) { - int pair[2]; - sigset_t set, oldset; - struct client *c = NULL; - char *cause = NULL; + int fd; + sigset_t set, oldset; + struct client *c = NULL; + char *cause = NULL; sigfillset(&set); sigprocmask(SIG_BLOCK, &set, &oldset); if (~flags & CLIENT_NOFORK) { - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) - fatal("socketpair failed"); - - switch (fork()) { - case -1: - fatal("fork failed"); - case 0: - break; - default: + if (proc_fork_and_daemon(&fd) != 0) { sigprocmask(SIG_SETMASK, &oldset, NULL); - close(pair[1]); - return (pair[0]); + return (fd); } - close(pair[0]); - if (daemon(1, 0) != 0) - fatal("daemon failed"); } - - server_client_flags = flags; proc_clear_signals(client, 0); + server_client_flags = flags; if (event_reinit(base) != 0) fatalx("event_reinit failed"); @@ -213,7 +200,7 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, if (server_fd != -1) server_update_socket(); if (~flags & CLIENT_NOFORK) - c = server_client_create(pair[1]); + c = server_client_create(fd); else options_set_number(global_options, "exit-empty", 0); diff --git a/tmux.h b/tmux.h index 985b8858..8f83c218 100644 --- a/tmux.h +++ b/tmux.h @@ -1899,6 +1899,7 @@ struct tmuxpeer *proc_add_peer(struct tmuxproc *, int, void proc_remove_peer(struct tmuxpeer *); void proc_kill_peer(struct tmuxpeer *); void proc_toggle_log(struct tmuxproc *); +pid_t proc_fork_and_daemon(int *); /* cfg.c */ extern int cfg_finished; @@ -2389,6 +2390,7 @@ void file_write(struct client *, const char *, int, const void *, size_t, client_file_cb, void *); void file_read(struct client *, const char *, client_file_cb, void *); void file_push(struct client_file *); +int file_write_left(struct client_files *); void file_write_open(struct client_files *, struct tmuxpeer *, struct imsg *, int, int, client_file_cb, void *); void file_write_data(struct client_files *, struct imsg *); From 632636dba535468d8266ad44c099f1217f1e3ea5 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 12 Feb 2021 06:52:48 +0000 Subject: [PATCH 68/90] Do not care about the server socket closing if exiting anyway. --- client.c | 6 ++++-- file.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client.c b/client.c index 74dc4602..5a454ffe 100644 --- a/client.c +++ b/client.c @@ -553,8 +553,10 @@ static void client_dispatch(struct imsg *imsg, __unused void *arg) { if (imsg == NULL) { - client_exitreason = CLIENT_EXIT_LOST_SERVER; - client_exitval = 1; + if (!client_exitflag) { + client_exitreason = CLIENT_EXIT_LOST_SERVER; + client_exitval = 1; + } proc_exit(client_proc); return; } diff --git a/file.c b/file.c index 8eb4e178..8f497b38 100644 --- a/file.c +++ b/file.c @@ -485,7 +485,7 @@ file_write_left(struct client_files *files) size_t left; int waiting = 0; - RB_FOREACH (cf, client_files, files) { + RB_FOREACH(cf, client_files, files) { if (cf->event == NULL) continue; left = EVBUFFER_LENGTH(cf->event->output); From 6642706f7baab2d7501ca8ac16659ddab1d729ca Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Feb 2021 09:39:37 +0000 Subject: [PATCH 69/90] Support X11 colour names and some other variations for OSC 10/11, also add OSC 110 and 111. GitHub issue 2567. --- colour.c | 611 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- format.c | 24 +++ input.c | 135 +++++++----- tmux.1 | 4 +- tmux.h | 1 + 5 files changed, 721 insertions(+), 54 deletions(-) diff --git a/colour.c b/colour.c index ee4b95db..1d1729ca 100644 --- a/colour.c +++ b/colour.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "tmux.h" @@ -111,6 +112,9 @@ colour_tostring(int c) static char s[32]; u_char r, g, b; + if (c == -1) + return ("invalid"); + if (c & COLOUR_FLAG_RGB) { colour_split_rgb(c, &r, &g, &b); xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b); @@ -233,7 +237,7 @@ colour_fromstring(const char *s) return (96); if (strcasecmp(s, "brightwhite") == 0 || strcmp(s, "97") == 0) return (97); - return (-1); + return (colour_byname(s)); } /* Convert 256 colour to RGB colour. */ @@ -335,3 +339,608 @@ colour_256to16(int c) return (table[c & 0xff]); } + +/* Get colour by X11 colour name. */ +int +colour_byname(const char *name) +{ + static const struct { + const char *name; + int c; + } colours[] = { + { "AliceBlue", 0xf0f8ff }, + { "AntiqueWhite", 0xfaebd7 }, + { "AntiqueWhite1", 0xffefdb }, + { "AntiqueWhite2", 0xeedfcc }, + { "AntiqueWhite3", 0xcdc0b0 }, + { "AntiqueWhite4", 0x8b8378 }, + { "BlanchedAlmond", 0xffebcd }, + { "BlueViolet", 0x8a2be2 }, + { "CadetBlue", 0x5f9ea0 }, + { "CadetBlue1", 0x98f5ff }, + { "CadetBlue2", 0x8ee5ee }, + { "CadetBlue3", 0x7ac5cd }, + { "CadetBlue4", 0x53868b }, + { "CornflowerBlue", 0x6495ed }, + { "DarkBlue", 0x00008b }, + { "DarkCyan", 0x008b8b }, + { "DarkGoldenrod", 0xb8860b }, + { "DarkGoldenrod1", 0xffb90f }, + { "DarkGoldenrod2", 0xeead0e }, + { "DarkGoldenrod3", 0xcd950c }, + { "DarkGoldenrod4", 0x8b6508 }, + { "DarkGray", 0xa9a9a9 }, + { "DarkGreen", 0x006400 }, + { "DarkGrey", 0xa9a9a9 }, + { "DarkKhaki", 0xbdb76b }, + { "DarkMagenta", 0x8b008b }, + { "DarkOliveGreen", 0x556b2f }, + { "DarkOliveGreen1", 0xcaff70 }, + { "DarkOliveGreen2", 0xbcee68 }, + { "DarkOliveGreen3", 0xa2cd5a }, + { "DarkOliveGreen4", 0x6e8b3d }, + { "DarkOrange", 0xff8c00 }, + { "DarkOrange1", 0xff7f00 }, + { "DarkOrange2", 0xee7600 }, + { "DarkOrange3", 0xcd6600 }, + { "DarkOrange4", 0x8b4500 }, + { "DarkOrchid", 0x9932cc }, + { "DarkOrchid1", 0xbf3eff }, + { "DarkOrchid2", 0xb23aee }, + { "DarkOrchid3", 0x9a32cd }, + { "DarkOrchid4", 0x68228b }, + { "DarkRed", 0x8b0000 }, + { "DarkSalmon", 0xe9967a }, + { "DarkSeaGreen", 0x8fbc8f }, + { "DarkSeaGreen1", 0xc1ffc1 }, + { "DarkSeaGreen2", 0xb4eeb4 }, + { "DarkSeaGreen3", 0x9bcd9b }, + { "DarkSeaGreen4", 0x698b69 }, + { "DarkSlateBlue", 0x483d8b }, + { "DarkSlateGray", 0x2f4f4f }, + { "DarkSlateGray1", 0x97ffff }, + { "DarkSlateGray2", 0x8deeee }, + { "DarkSlateGray3", 0x79cdcd }, + { "DarkSlateGray4", 0x528b8b }, + { "DarkSlateGrey", 0x2f4f4f }, + { "DarkTurquoise", 0x00ced1 }, + { "DarkViolet", 0x9400d3 }, + { "DeepPink", 0xff1493 }, + { "DeepPink1", 0xff1493 }, + { "DeepPink2", 0xee1289 }, + { "DeepPink3", 0xcd1076 }, + { "DeepPink4", 0x8b0a50 }, + { "DeepSkyBlue", 0x00bfff }, + { "DeepSkyBlue1", 0x00bfff }, + { "DeepSkyBlue2", 0x00b2ee }, + { "DeepSkyBlue3", 0x009acd }, + { "DeepSkyBlue4", 0x00688b }, + { "DimGray", 0x696969 }, + { "DimGrey", 0x696969 }, + { "DodgerBlue", 0x1e90ff }, + { "DodgerBlue1", 0x1e90ff }, + { "DodgerBlue2", 0x1c86ee }, + { "DodgerBlue3", 0x1874cd }, + { "DodgerBlue4", 0x104e8b }, + { "FloralWhite", 0xfffaf0 }, + { "ForestGreen", 0x228b22 }, + { "GhostWhite", 0xf8f8ff }, + { "GreenYellow", 0xadff2f }, + { "HotPink", 0xff69b4 }, + { "HotPink1", 0xff6eb4 }, + { "HotPink2", 0xee6aa7 }, + { "HotPink3", 0xcd6090 }, + { "HotPink4", 0x8b3a62 }, + { "IndianRed", 0xcd5c5c }, + { "IndianRed1", 0xff6a6a }, + { "IndianRed2", 0xee6363 }, + { "IndianRed3", 0xcd5555 }, + { "IndianRed4", 0x8b3a3a }, + { "LavenderBlush", 0xfff0f5 }, + { "LavenderBlush1", 0xfff0f5 }, + { "LavenderBlush2", 0xeee0e5 }, + { "LavenderBlush3", 0xcdc1c5 }, + { "LavenderBlush4", 0x8b8386 }, + { "LawnGreen", 0x7cfc00 }, + { "LemonChiffon", 0xfffacd }, + { "LemonChiffon1", 0xfffacd }, + { "LemonChiffon2", 0xeee9bf }, + { "LemonChiffon3", 0xcdc9a5 }, + { "LemonChiffon4", 0x8b8970 }, + { "LightBlue", 0xadd8e6 }, + { "LightBlue1", 0xbfefff }, + { "LightBlue2", 0xb2dfee }, + { "LightBlue3", 0x9ac0cd }, + { "LightBlue4", 0x68838b }, + { "LightCoral", 0xf08080 }, + { "LightCyan", 0xe0ffff }, + { "LightCyan1", 0xe0ffff }, + { "LightCyan2", 0xd1eeee }, + { "LightCyan3", 0xb4cdcd }, + { "LightCyan4", 0x7a8b8b }, + { "LightGoldenrod", 0xeedd82 }, + { "LightGoldenrod1", 0xffec8b }, + { "LightGoldenrod2", 0xeedc82 }, + { "LightGoldenrod3", 0xcdbe70 }, + { "LightGoldenrod4", 0x8b814c }, + { "LightGoldenrodYellow", 0xfafad2 }, + { "LightGray", 0xd3d3d3 }, + { "LightGreen", 0x90ee90 }, + { "LightGrey", 0xd3d3d3 }, + { "LightPink", 0xffb6c1 }, + { "LightPink1", 0xffaeb9 }, + { "LightPink2", 0xeea2ad }, + { "LightPink3", 0xcd8c95 }, + { "LightPink4", 0x8b5f65 }, + { "LightSalmon", 0xffa07a }, + { "LightSalmon1", 0xffa07a }, + { "LightSalmon2", 0xee9572 }, + { "LightSalmon3", 0xcd8162 }, + { "LightSalmon4", 0x8b5742 }, + { "LightSeaGreen", 0x20b2aa }, + { "LightSkyBlue", 0x87cefa }, + { "LightSkyBlue1", 0xb0e2ff }, + { "LightSkyBlue2", 0xa4d3ee }, + { "LightSkyBlue3", 0x8db6cd }, + { "LightSkyBlue4", 0x607b8b }, + { "LightSlateBlue", 0x8470ff }, + { "LightSlateGray", 0x778899 }, + { "LightSlateGrey", 0x778899 }, + { "LightSteelBlue", 0xb0c4de }, + { "LightSteelBlue1", 0xcae1ff }, + { "LightSteelBlue2", 0xbcd2ee }, + { "LightSteelBlue3", 0xa2b5cd }, + { "LightSteelBlue4", 0x6e7b8b }, + { "LightYellow", 0xffffe0 }, + { "LightYellow1", 0xffffe0 }, + { "LightYellow2", 0xeeeed1 }, + { "LightYellow3", 0xcdcdb4 }, + { "LightYellow4", 0x8b8b7a }, + { "LimeGreen", 0x32cd32 }, + { "MediumAquamarine", 0x66cdaa }, + { "MediumBlue", 0x0000cd }, + { "MediumOrchid", 0xba55d3 }, + { "MediumOrchid1", 0xe066ff }, + { "MediumOrchid2", 0xd15fee }, + { "MediumOrchid3", 0xb452cd }, + { "MediumOrchid4", 0x7a378b }, + { "MediumPurple", 0x9370db }, + { "MediumPurple1", 0xab82ff }, + { "MediumPurple2", 0x9f79ee }, + { "MediumPurple3", 0x8968cd }, + { "MediumPurple4", 0x5d478b }, + { "MediumSeaGreen", 0x3cb371 }, + { "MediumSlateBlue", 0x7b68ee }, + { "MediumSpringGreen", 0x00fa9a }, + { "MediumTurquoise", 0x48d1cc }, + { "MediumVioletRed", 0xc71585 }, + { "MidnightBlue", 0x191970 }, + { "MintCream", 0xf5fffa }, + { "MistyRose", 0xffe4e1 }, + { "MistyRose1", 0xffe4e1 }, + { "MistyRose2", 0xeed5d2 }, + { "MistyRose3", 0xcdb7b5 }, + { "MistyRose4", 0x8b7d7b }, + { "NavajoWhite", 0xffdead }, + { "NavajoWhite1", 0xffdead }, + { "NavajoWhite2", 0xeecfa1 }, + { "NavajoWhite3", 0xcdb38b }, + { "NavajoWhite4", 0x8b795e }, + { "NavyBlue", 0x000080 }, + { "OldLace", 0xfdf5e6 }, + { "OliveDrab", 0x6b8e23 }, + { "OliveDrab1", 0xc0ff3e }, + { "OliveDrab2", 0xb3ee3a }, + { "OliveDrab3", 0x9acd32 }, + { "OliveDrab4", 0x698b22 }, + { "OrangeRed", 0xff4500 }, + { "OrangeRed1", 0xff4500 }, + { "OrangeRed2", 0xee4000 }, + { "OrangeRed3", 0xcd3700 }, + { "OrangeRed4", 0x8b2500 }, + { "PaleGoldenrod", 0xeee8aa }, + { "PaleGreen", 0x98fb98 }, + { "PaleGreen1", 0x9aff9a }, + { "PaleGreen2", 0x90ee90 }, + { "PaleGreen3", 0x7ccd7c }, + { "PaleGreen4", 0x548b54 }, + { "PaleTurquoise", 0xafeeee }, + { "PaleTurquoise1", 0xbbffff }, + { "PaleTurquoise2", 0xaeeeee }, + { "PaleTurquoise3", 0x96cdcd }, + { "PaleTurquoise4", 0x668b8b }, + { "PaleVioletRed", 0xdb7093 }, + { "PaleVioletRed1", 0xff82ab }, + { "PaleVioletRed2", 0xee799f }, + { "PaleVioletRed3", 0xcd6889 }, + { "PaleVioletRed4", 0x8b475d }, + { "PapayaWhip", 0xffefd5 }, + { "PeachPuff", 0xffdab9 }, + { "PeachPuff1", 0xffdab9 }, + { "PeachPuff2", 0xeecbad }, + { "PeachPuff3", 0xcdaf95 }, + { "PeachPuff4", 0x8b7765 }, + { "PowderBlue", 0xb0e0e6 }, + { "RebeccaPurple", 0x663399 }, + { "RosyBrown", 0xbc8f8f }, + { "RosyBrown1", 0xffc1c1 }, + { "RosyBrown2", 0xeeb4b4 }, + { "RosyBrown3", 0xcd9b9b }, + { "RosyBrown4", 0x8b6969 }, + { "RoyalBlue", 0x4169e1 }, + { "RoyalBlue1", 0x4876ff }, + { "RoyalBlue2", 0x436eee }, + { "RoyalBlue3", 0x3a5fcd }, + { "RoyalBlue4", 0x27408b }, + { "SaddleBrown", 0x8b4513 }, + { "SandyBrown", 0xf4a460 }, + { "SeaGreen", 0x2e8b57 }, + { "SeaGreen1", 0x54ff9f }, + { "SeaGreen2", 0x4eee94 }, + { "SeaGreen3", 0x43cd80 }, + { "SeaGreen4", 0x2e8b57 }, + { "SkyBlue", 0x87ceeb }, + { "SkyBlue1", 0x87ceff }, + { "SkyBlue2", 0x7ec0ee }, + { "SkyBlue3", 0x6ca6cd }, + { "SkyBlue4", 0x4a708b }, + { "SlateBlue", 0x6a5acd }, + { "SlateBlue1", 0x836fff }, + { "SlateBlue2", 0x7a67ee }, + { "SlateBlue3", 0x6959cd }, + { "SlateBlue4", 0x473c8b }, + { "SlateGray", 0x708090 }, + { "SlateGray1", 0xc6e2ff }, + { "SlateGray2", 0xb9d3ee }, + { "SlateGray3", 0x9fb6cd }, + { "SlateGray4", 0x6c7b8b }, + { "SlateGrey", 0x708090 }, + { "SpringGreen", 0x00ff7f }, + { "SpringGreen1", 0x00ff7f }, + { "SpringGreen2", 0x00ee76 }, + { "SpringGreen3", 0x00cd66 }, + { "SpringGreen4", 0x008b45 }, + { "SteelBlue", 0x4682b4 }, + { "SteelBlue1", 0x63b8ff }, + { "SteelBlue2", 0x5cacee }, + { "SteelBlue3", 0x4f94cd }, + { "SteelBlue4", 0x36648b }, + { "VioletRed", 0xd02090 }, + { "VioletRed1", 0xff3e96 }, + { "VioletRed2", 0xee3a8c }, + { "VioletRed3", 0xcd3278 }, + { "VioletRed4", 0x8b2252 }, + { "WebGray", 0x808080 }, + { "WebGreen", 0x008000 }, + { "WebGrey", 0x808080 }, + { "WebMaroon", 0x800000 }, + { "WebPurple", 0x800080 }, + { "WhiteSmoke", 0xf5f5f5 }, + { "X11Gray", 0xbebebe }, + { "X11Green", 0x00ff00 }, + { "X11Grey", 0xbebebe }, + { "X11Maroon", 0xb03060 }, + { "X11Purple", 0xa020f0 }, + { "YellowGreen", 0x9acd32 }, + { "alice blue", 0xf0f8ff }, + { "antique white", 0xfaebd7 }, + { "aqua", 0x00ffff }, + { "aquamarine", 0x7fffd4 }, + { "aquamarine1", 0x7fffd4 }, + { "aquamarine2", 0x76eec6 }, + { "aquamarine3", 0x66cdaa }, + { "aquamarine4", 0x458b74 }, + { "azure", 0xf0ffff }, + { "azure1", 0xf0ffff }, + { "azure2", 0xe0eeee }, + { "azure3", 0xc1cdcd }, + { "azure4", 0x838b8b }, + { "beige", 0xf5f5dc }, + { "bisque", 0xffe4c4 }, + { "bisque1", 0xffe4c4 }, + { "bisque2", 0xeed5b7 }, + { "bisque3", 0xcdb79e }, + { "bisque4", 0x8b7d6b }, + { "black", 0x000000 }, + { "blanched almond", 0xffebcd }, + { "blue violet", 0x8a2be2 }, + { "blue", 0x0000ff }, + { "blue1", 0x0000ff }, + { "blue2", 0x0000ee }, + { "blue3", 0x0000cd }, + { "blue4", 0x00008b }, + { "brown", 0xa52a2a }, + { "brown1", 0xff4040 }, + { "brown2", 0xee3b3b }, + { "brown3", 0xcd3333 }, + { "brown4", 0x8b2323 }, + { "burlywood", 0xdeb887 }, + { "burlywood1", 0xffd39b }, + { "burlywood2", 0xeec591 }, + { "burlywood3", 0xcdaa7d }, + { "burlywood4", 0x8b7355 }, + { "cadet blue", 0x5f9ea0 }, + { "chartreuse", 0x7fff00 }, + { "chartreuse1", 0x7fff00 }, + { "chartreuse2", 0x76ee00 }, + { "chartreuse3", 0x66cd00 }, + { "chartreuse4", 0x458b00 }, + { "chocolate", 0xd2691e }, + { "chocolate1", 0xff7f24 }, + { "chocolate2", 0xee7621 }, + { "chocolate3", 0xcd661d }, + { "chocolate4", 0x8b4513 }, + { "coral", 0xff7f50 }, + { "coral1", 0xff7256 }, + { "coral2", 0xee6a50 }, + { "coral3", 0xcd5b45 }, + { "coral4", 0x8b3e2f }, + { "cornflower blue", 0x6495ed }, + { "cornsilk", 0xfff8dc }, + { "cornsilk1", 0xfff8dc }, + { "cornsilk2", 0xeee8cd }, + { "cornsilk3", 0xcdc8b1 }, + { "cornsilk4", 0x8b8878 }, + { "crimson", 0xdc143c }, + { "cyan", 0x00ffff }, + { "cyan1", 0x00ffff }, + { "cyan2", 0x00eeee }, + { "cyan3", 0x00cdcd }, + { "cyan4", 0x008b8b }, + { "dark blue", 0x00008b }, + { "dark cyan", 0x008b8b }, + { "dark goldenrod", 0xb8860b }, + { "dark gray", 0xa9a9a9 }, + { "dark green", 0x006400 }, + { "dark grey", 0xa9a9a9 }, + { "dark khaki", 0xbdb76b }, + { "dark magenta", 0x8b008b }, + { "dark olive green", 0x556b2f }, + { "dark orange", 0xff8c00 }, + { "dark orchid", 0x9932cc }, + { "dark red", 0x8b0000 }, + { "dark salmon", 0xe9967a }, + { "dark sea green", 0x8fbc8f }, + { "dark slate blue", 0x483d8b }, + { "dark slate gray", 0x2f4f4f }, + { "dark slate grey", 0x2f4f4f }, + { "dark turquoise", 0x00ced1 }, + { "dark violet", 0x9400d3 }, + { "deep pink", 0xff1493 }, + { "deep sky blue", 0x00bfff }, + { "dim gray", 0x696969 }, + { "dim grey", 0x696969 }, + { "dodger blue", 0x1e90ff }, + { "firebrick", 0xb22222 }, + { "firebrick1", 0xff3030 }, + { "firebrick2", 0xee2c2c }, + { "firebrick3", 0xcd2626 }, + { "firebrick4", 0x8b1a1a }, + { "floral white", 0xfffaf0 }, + { "forest green", 0x228b22 }, + { "fuchsia", 0xff00ff }, + { "gainsboro", 0xdcdcdc }, + { "ghost white", 0xf8f8ff }, + { "gold", 0xffd700 }, + { "gold1", 0xffd700 }, + { "gold2", 0xeec900 }, + { "gold3", 0xcdad00 }, + { "gold4", 0x8b7500 }, + { "goldenrod", 0xdaa520 }, + { "goldenrod1", 0xffc125 }, + { "goldenrod2", 0xeeb422 }, + { "goldenrod3", 0xcd9b1d }, + { "goldenrod4", 0x8b6914 }, + { "green yellow", 0xadff2f }, + { "green", 0x00ff00 }, + { "green1", 0x00ff00 }, + { "green2", 0x00ee00 }, + { "green3", 0x00cd00 }, + { "green4", 0x008b00 }, + { "honeydew", 0xf0fff0 }, + { "honeydew1", 0xf0fff0 }, + { "honeydew2", 0xe0eee0 }, + { "honeydew3", 0xc1cdc1 }, + { "honeydew4", 0x838b83 }, + { "hot pink", 0xff69b4 }, + { "indian red", 0xcd5c5c }, + { "indigo", 0x4b0082 }, + { "ivory", 0xfffff0 }, + { "ivory1", 0xfffff0 }, + { "ivory2", 0xeeeee0 }, + { "ivory3", 0xcdcdc1 }, + { "ivory4", 0x8b8b83 }, + { "khaki", 0xf0e68c }, + { "khaki1", 0xfff68f }, + { "khaki2", 0xeee685 }, + { "khaki3", 0xcdc673 }, + { "khaki4", 0x8b864e }, + { "lavender blush", 0xfff0f5 }, + { "lavender", 0xe6e6fa }, + { "lawn green", 0x7cfc00 }, + { "lemon chiffon", 0xfffacd }, + { "light blue", 0xadd8e6 }, + { "light coral", 0xf08080 }, + { "light cyan", 0xe0ffff }, + { "light goldenrod yellow", 0xfafad2 }, + { "light goldenrod", 0xeedd82 }, + { "light gray", 0xd3d3d3 }, + { "light green", 0x90ee90 }, + { "light grey", 0xd3d3d3 }, + { "light pink", 0xffb6c1 }, + { "light salmon", 0xffa07a }, + { "light sea green", 0x20b2aa }, + { "light sky blue", 0x87cefa }, + { "light slate blue", 0x8470ff }, + { "light slate gray", 0x778899 }, + { "light slate grey", 0x778899 }, + { "light steel blue", 0xb0c4de }, + { "light yellow", 0xffffe0 }, + { "lime green", 0x32cd32 }, + { "lime", 0x00ff00 }, + { "linen", 0xfaf0e6 }, + { "magenta", 0xff00ff }, + { "magenta1", 0xff00ff }, + { "magenta2", 0xee00ee }, + { "magenta3", 0xcd00cd }, + { "magenta4", 0x8b008b }, + { "maroon", 0xb03060 }, + { "maroon1", 0xff34b3 }, + { "maroon2", 0xee30a7 }, + { "maroon3", 0xcd2990 }, + { "maroon4", 0x8b1c62 }, + { "medium aquamarine", 0x66cdaa }, + { "medium blue", 0x0000cd }, + { "medium orchid", 0xba55d3 }, + { "medium purple", 0x9370db }, + { "medium sea green", 0x3cb371 }, + { "medium slate blue", 0x7b68ee }, + { "medium spring green", 0x00fa9a }, + { "medium turquoise", 0x48d1cc }, + { "medium violet red", 0xc71585 }, + { "midnight blue", 0x191970 }, + { "mint cream", 0xf5fffa }, + { "misty rose", 0xffe4e1 }, + { "moccasin", 0xffe4b5 }, + { "navajo white", 0xffdead }, + { "navy blue", 0x000080 }, + { "navy", 0x000080 }, + { "old lace", 0xfdf5e6 }, + { "olive drab", 0x6b8e23 }, + { "olive", 0x808000 }, + { "orange red", 0xff4500 }, + { "orange", 0xffa500 }, + { "orange1", 0xffa500 }, + { "orange2", 0xee9a00 }, + { "orange3", 0xcd8500 }, + { "orange4", 0x8b5a00 }, + { "orchid", 0xda70d6 }, + { "orchid1", 0xff83fa }, + { "orchid2", 0xee7ae9 }, + { "orchid3", 0xcd69c9 }, + { "orchid4", 0x8b4789 }, + { "pale goldenrod", 0xeee8aa }, + { "pale green", 0x98fb98 }, + { "pale turquoise", 0xafeeee }, + { "pale violet red", 0xdb7093 }, + { "papaya whip", 0xffefd5 }, + { "peach puff", 0xffdab9 }, + { "peru", 0xcd853f }, + { "pink", 0xffc0cb }, + { "pink1", 0xffb5c5 }, + { "pink2", 0xeea9b8 }, + { "pink3", 0xcd919e }, + { "pink4", 0x8b636c }, + { "plum", 0xdda0dd }, + { "plum1", 0xffbbff }, + { "plum2", 0xeeaeee }, + { "plum3", 0xcd96cd }, + { "plum4", 0x8b668b }, + { "powder blue", 0xb0e0e6 }, + { "purple", 0xa020f0 }, + { "purple1", 0x9b30ff }, + { "purple2", 0x912cee }, + { "purple3", 0x7d26cd }, + { "purple4", 0x551a8b }, + { "rebecca purple", 0x663399 }, + { "red", 0xff0000 }, + { "red1", 0xff0000 }, + { "red2", 0xee0000 }, + { "red3", 0xcd0000 }, + { "red4", 0x8b0000 }, + { "rosy brown", 0xbc8f8f }, + { "royal blue", 0x4169e1 }, + { "saddle brown", 0x8b4513 }, + { "salmon", 0xfa8072 }, + { "salmon1", 0xff8c69 }, + { "salmon2", 0xee8262 }, + { "salmon3", 0xcd7054 }, + { "salmon4", 0x8b4c39 }, + { "sandy brown", 0xf4a460 }, + { "sea green", 0x2e8b57 }, + { "seashell", 0xfff5ee }, + { "seashell1", 0xfff5ee }, + { "seashell2", 0xeee5de }, + { "seashell3", 0xcdc5bf }, + { "seashell4", 0x8b8682 }, + { "sienna", 0xa0522d }, + { "sienna1", 0xff8247 }, + { "sienna2", 0xee7942 }, + { "sienna3", 0xcd6839 }, + { "sienna4", 0x8b4726 }, + { "silver", 0xc0c0c0 }, + { "sky blue", 0x87ceeb }, + { "slate blue", 0x6a5acd }, + { "slate gray", 0x708090 }, + { "slate grey", 0x708090 }, + { "snow", 0xfffafa }, + { "snow1", 0xfffafa }, + { "snow2", 0xeee9e9 }, + { "snow3", 0xcdc9c9 }, + { "snow4", 0x8b8989 }, + { "spring green", 0x00ff7f }, + { "steel blue", 0x4682b4 }, + { "tan", 0xd2b48c }, + { "tan1", 0xffa54f }, + { "tan2", 0xee9a49 }, + { "tan3", 0xcd853f }, + { "tan4", 0x8b5a2b }, + { "teal", 0x008080 }, + { "thistle", 0xd8bfd8 }, + { "thistle1", 0xffe1ff }, + { "thistle2", 0xeed2ee }, + { "thistle3", 0xcdb5cd }, + { "thistle4", 0x8b7b8b }, + { "tomato", 0xff6347 }, + { "tomato1", 0xff6347 }, + { "tomato2", 0xee5c42 }, + { "tomato3", 0xcd4f39 }, + { "tomato4", 0x8b3626 }, + { "turquoise", 0x40e0d0 }, + { "turquoise1", 0x00f5ff }, + { "turquoise2", 0x00e5ee }, + { "turquoise3", 0x00c5cd }, + { "turquoise4", 0x00868b }, + { "violet red", 0xd02090 }, + { "violet", 0xee82ee }, + { "web gray", 0x808080 }, + { "web green", 0x008000 }, + { "web grey", 0x808080 }, + { "web maroon", 0x800000 }, + { "web purple", 0x800080 }, + { "wheat", 0xf5deb3 }, + { "wheat1", 0xffe7ba }, + { "wheat2", 0xeed8ae }, + { "wheat3", 0xcdba96 }, + { "wheat4", 0x8b7e66 }, + { "white smoke", 0xf5f5f5 }, + { "white", 0xffffff }, + { "x11 gray", 0xbebebe }, + { "x11 green", 0x00ff00 }, + { "x11 grey", 0xbebebe }, + { "x11 maroon", 0xb03060 }, + { "x11 purple", 0xa020f0 }, + { "yellow green", 0x9acd32 }, + { "yellow", 0xffff00 }, + { "yellow1", 0xffff00 }, + { "yellow2", 0xeeee00 }, + { "yellow3", 0xcdcd00 }, + { "yellow4", 0x8b8b00 } + }; + u_int i; + int c; + + if (strncmp(name, "grey", 4) == 0 || strncmp(name, "gray", 4) == 0) { + if (!isdigit((u_char)name[4])) + return (0xbebebe|COLOUR_FLAG_RGB); + c = round(2.55 * atoi(name + 4)); + if (c < 0 || c > 255) + return (-1); + return (colour_join_rgb(c, c, c)); + } + for (i = 0; i < nitems(colours); i++) { + if (strcasecmp(colours[i].name, name) == 0) + return (colours[i].c|COLOUR_FLAG_RGB); + } + return (-1); +} diff --git a/format.c b/format.c index 0e773498..47e0bb29 100644 --- a/format.c +++ b/format.c @@ -878,6 +878,28 @@ format_cb_pane_tabs(struct format_tree *ft) return (value); } +/* Callback for pane_fg. */ +static char * +format_cb_pane_fg(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + struct grid_cell gc; + + tty_default_colours(&gc, wp); + return (xstrdup(colour_tostring(gc.fg))); +} + +/* Callback for pane_bg. */ +static char * +format_cb_pane_bg(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + struct grid_cell gc; + + tty_default_colours(&gc, wp); + return (xstrdup(colour_tostring(gc.bg))); +} + /* Callback for session_group_list. */ static char * format_cb_session_group_list(struct format_tree *ft) @@ -3195,6 +3217,8 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) !!(wp->base.mode & MODE_MOUSE_SGR)); format_add_cb(ft, "pane_tabs", format_cb_pane_tabs); + format_add_cb(ft, "pane_fg", format_cb_pane_fg); + format_add_cb(ft, "pane_bg", format_cb_pane_bg); } /* Set default format keys for paste buffer. */ diff --git a/input.c b/input.c index 8b7ba08a..d22ee1a8 100644 --- a/input.c +++ b/input.c @@ -138,6 +138,8 @@ static void input_osc_10(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *); static void input_osc_52(struct input_ctx *, const char *); static void input_osc_104(struct input_ctx *, const char *); +static void input_osc_110(struct input_ctx *, const char *); +static void input_osc_111(struct input_ctx *, const char *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -2304,6 +2306,12 @@ input_exit_osc(struct input_ctx *ictx) case 104: input_osc_104(ictx, p); break; + case 110: + input_osc_110(ictx, p); + break; + case 111: + input_osc_111(ictx, p); + break; case 112: if (*p == '\0') /* no arguments allowed */ screen_set_cursor_colour(sctx->s, ""); @@ -2422,50 +2430,41 @@ input_top_bit_set(struct input_ctx *ictx) /* Parse colour from OSC. */ static int -input_osc_parse_colour(const char *p, u_int *r, u_int *g, u_int *b) +input_osc_parse_colour(const char *p) { - u_int rsize, gsize, bsize; - const char *cp, *s = p; + double c, m, y, k = 0; + u_int r, g, b; + size_t len = strlen(p); + int colour = -1; + char *copy; - if (sscanf(p, "rgb:%x/%x/%x", r, g, b) != 3) - return (0); - p += 4; - - cp = strchr(p, '/'); - rsize = cp - p; - if (rsize == 1) - (*r) = (*r) | ((*r) << 4); - else if (rsize == 3) - (*r) >>= 4; - else if (rsize == 4) - (*r) >>= 8; - else if (rsize != 2) - return (0); - - p = cp + 1; - cp = strchr(p, '/'); - gsize = cp - p; - if (gsize == 1) - (*g) = (*g) | ((*g) << 4); - else if (gsize == 3) - (*g) >>= 4; - else if (gsize == 4) - (*g) >>= 8; - else if (gsize != 2) - return (0); - - bsize = strlen(cp + 1); - if (bsize == 1) - (*b) = (*b) | ((*b) << 4); - else if (bsize == 3) - (*b) >>= 4; - else if (bsize == 4) - (*b) >>= 8; - else if (bsize != 2) - return (0); - - log_debug("%s: %s = %02x%02x%02x", __func__, s, *r, *g, *b); - return (1); + if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || + (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || + sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) + colour = colour_join_rgb(r, g, b); + else if ((len == 18 && + sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || + (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) + colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); + else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || + sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && + c >= 0 && c <= 1 && m >= 0 && m <= 1 && + y >= 0 && y <= 1 && k >= 0 && k <= 1) { + colour = colour_join_rgb( + (1 - c) * (1 - k) * 255, + (1 - m) * (1 - k) * 255, + (1 - y) * (1 - k) * 255); + } else { + while (*p == ' ') + p++; + while (len != 0 && p[len - 1] == ' ') + len--; + copy = xstrndup(p, len); + colour = colour_byname(copy); + free(copy); + } + log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); + return (colour); } /* Reply to a colour request. */ @@ -2493,7 +2492,7 @@ input_osc_4(struct input_ctx *ictx, const char *p) struct window_pane *wp = ictx->wp; char *copy, *s, *next = NULL; long idx; - u_int r, g, b; + int c; if (wp == NULL) return; @@ -2507,12 +2506,12 @@ input_osc_4(struct input_ctx *ictx, const char *p) goto bad; s = strsep(&next, ";"); - if (!input_osc_parse_colour(s, &r, &g, &b)) { + if ((c = input_osc_parse_colour(s)) == -1) { s = next; continue; } - window_pane_set_palette(wp, idx, colour_join_rgb(r, g, b)); + window_pane_set_palette(wp, idx, c); s = next; } @@ -2530,7 +2529,7 @@ input_osc_10(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; struct grid_cell defaults; - u_int r, g, b; + int c; if (wp == NULL) return; @@ -2541,9 +2540,9 @@ input_osc_10(struct input_ctx *ictx, const char *p) return; } - if (!input_osc_parse_colour(p, &r, &g, &b)) + if ((c = input_osc_parse_colour(p)) == -1) goto bad; - wp->fg = colour_join_rgb(r, g, b); + wp->fg = c; wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); return; @@ -2552,13 +2551,29 @@ bad: log_debug("bad OSC 10: %s", p); } +/* Handle the OSC 110 sequence for resetting background colour. */ +static void +input_osc_110(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + + if (wp == NULL) + return; + + if (*p != '\0') + return; + + wp->fg = 8; + wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); +} + /* Handle the OSC 11 sequence for setting and querying background colour. */ static void input_osc_11(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; struct grid_cell defaults; - u_int r, g, b; + int c; if (wp == NULL) return; @@ -2569,9 +2584,9 @@ input_osc_11(struct input_ctx *ictx, const char *p) return; } - if (!input_osc_parse_colour(p, &r, &g, &b)) - goto bad; - wp->bg = colour_join_rgb(r, g, b); + if ((c = input_osc_parse_colour(p)) == -1) + goto bad; + wp->bg = c; wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); return; @@ -2580,6 +2595,22 @@ bad: log_debug("bad OSC 11: %s", p); } +/* Handle the OSC 111 sequence for resetting background colour. */ +static void +input_osc_111(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + + if (wp == NULL) + return; + + if (*p != '\0') + return; + + wp->bg = 8; + wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); +} + /* Handle the OSC 52 sequence for setting the clipboard. */ static void input_osc_52(struct input_ctx *ictx, const char *p) diff --git a/tmux.1 b/tmux.1 index e1891009..9150c4a6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4778,9 +4778,9 @@ The following variables are available, where appropriate: .It Li "client_prefix" Ta "" Ta "1 if prefix key has been pressed" .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_termfeatures" Ta "" Ta "Terminal features of client, if any" .It Li "client_termname" Ta "" Ta "Terminal name of client" .It Li "client_termtype" Ta "" Ta "Terminal type of client, if available" -.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any" .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" @@ -4828,11 +4828,13 @@ The following variables are available, where appropriate: .It Li "pane_at_left" Ta "" Ta "1 if pane is at the left of window" .It Li "pane_at_right" Ta "" Ta "1 if pane is at the right of window" .It Li "pane_at_top" Ta "" Ta "1 if pane is at the top of window" +.It Li "pane_bg" Ta "" Ta "Pane background colour" .It Li "pane_bottom" Ta "" Ta "Bottom of pane" .It Li "pane_current_command" Ta "" Ta "Current command if available" .It Li "pane_current_path" Ta "" Ta "Current path if available" .It Li "pane_dead" Ta "" Ta "1 if pane is dead" .It Li "pane_dead_status" Ta "" Ta "Exit status of process in dead pane" +.It Li "pane_fg" Ta "" Ta "Pane foreground colour" .It Li "pane_format" Ta "" Ta "1 if format is for a pane" .It Li "pane_height" Ta "" Ta "Height of pane" .It Li "pane_id" Ta "#D" Ta "Unique pane ID" diff --git a/tmux.h b/tmux.h index 8f83c218..18921f6e 100644 --- a/tmux.h +++ b/tmux.h @@ -2526,6 +2526,7 @@ const char *colour_tostring(int); int colour_fromstring(const char *s); int colour_256toRGB(int); int colour_256to16(int); +int colour_byname(const char *); /* attributes.c */ const char *attributes_tostring(int); From 0526d074d0170ad248b06187b64f4e44a0c05dcc Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 15 Feb 2021 09:40:50 +0000 Subject: [PATCH 70/90] OSC 11 test. --- regress/osc-11colours.sh | 243 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 regress/osc-11colours.sh diff --git a/regress/osc-11colours.sh b/regress/osc-11colours.sh new file mode 100644 index 00000000..a0dd605d --- /dev/null +++ b/regress/osc-11colours.sh @@ -0,0 +1,243 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null + +$TMUX -vv new -d +$TMUX set -g remain-on-exit on + +do_test() { + $TMUX splitw "printf '$1'" + c="$($TMUX display -p '#{pane_bg}')" + $TMUX kill-pane + [ "$c" != "$2" ] && return 1 + return 0 +} + +do_test '\033]11;rgb:ff/ff/ff\007' '#ffffff' || exit 1 +do_test '\033]11;rgb:ff/ff/ff\007\033]111\007' 'default' || exit 1 + +do_test '\033]11;cmy:0.9373/0.6941/0.4549\007' '#0f4e8b' || exit 1 +do_test '\033]11;cmyk:0.88/0.44/0.00/0.45\007' '#104e8c' || exit 1 + +do_test '\033]11;16,78,139\007' '#104e8b' || exit 1 +do_test '\033]11;#104E8B\007' '#104e8b' || exit 1 +do_test '\033]11;#10004E008B00\007' '#104e8b' || exit 1 +do_test '\033]11;DodgerBlue4\007' '#104e8b' || exit 1 +do_test '\033]11;DodgerBlue4 \007' '#104e8b' || exit 1 +do_test '\033]11; DodgerBlue4\007' '#104e8b' || exit 1 +do_test '\033]11;rgb:10/4E/8B\007' '#104e8b' || exit 1 +do_test '\033]11;rgb:1000/4E00/8B00\007' '#104e8b' || exit 1 + +do_test '\033]11;grey\007' '#bebebe' || exit 1 +do_test '\033]11;grey0\007' '#000000' || exit 1 +do_test '\033]11;grey1\007' '#030303' || exit 1 +do_test '\033]11;grey2\007' '#050505' || exit 1 +do_test '\033]11;grey3\007' '#080808' || exit 1 +do_test '\033]11;grey4\007' '#0a0a0a' || exit 1 +do_test '\033]11;grey5\007' '#0d0d0d' || exit 1 +do_test '\033]11;grey6\007' '#0f0f0f' || exit 1 +do_test '\033]11;grey7\007' '#121212' || exit 1 +do_test '\033]11;grey8\007' '#141414' || exit 1 +do_test '\033]11;grey9\007' '#171717' || exit 1 +do_test '\033]11;grey10\007' '#1a1a1a' || exit 1 +do_test '\033]11;grey11\007' '#1c1c1c' || exit 1 +do_test '\033]11;grey12\007' '#1f1f1f' || exit 1 +do_test '\033]11;grey13\007' '#212121' || exit 1 +do_test '\033]11;grey14\007' '#242424' || exit 1 +do_test '\033]11;grey15\007' '#262626' || exit 1 +do_test '\033]11;grey16\007' '#292929' || exit 1 +do_test '\033]11;grey17\007' '#2b2b2b' || exit 1 +do_test '\033]11;grey18\007' '#2e2e2e' || exit 1 +do_test '\033]11;grey19\007' '#303030' || exit 1 +do_test '\033]11;grey20\007' '#333333' || exit 1 +do_test '\033]11;grey21\007' '#363636' || exit 1 +do_test '\033]11;grey22\007' '#383838' || exit 1 +do_test '\033]11;grey23\007' '#3b3b3b' || exit 1 +do_test '\033]11;grey24\007' '#3d3d3d' || exit 1 +do_test '\033]11;grey25\007' '#404040' || exit 1 +do_test '\033]11;grey26\007' '#424242' || exit 1 +do_test '\033]11;grey27\007' '#454545' || exit 1 +do_test '\033]11;grey28\007' '#474747' || exit 1 +do_test '\033]11;grey29\007' '#4a4a4a' || exit 1 +do_test '\033]11;grey30\007' '#4d4d4d' || exit 1 +do_test '\033]11;grey31\007' '#4f4f4f' || exit 1 +do_test '\033]11;grey32\007' '#525252' || exit 1 +do_test '\033]11;grey33\007' '#545454' || exit 1 +do_test '\033]11;grey34\007' '#575757' || exit 1 +do_test '\033]11;grey35\007' '#595959' || exit 1 +do_test '\033]11;grey36\007' '#5c5c5c' || exit 1 +do_test '\033]11;grey37\007' '#5e5e5e' || exit 1 +do_test '\033]11;grey38\007' '#616161' || exit 1 +do_test '\033]11;grey39\007' '#636363' || exit 1 +do_test '\033]11;grey40\007' '#666666' || exit 1 +do_test '\033]11;grey41\007' '#696969' || exit 1 +do_test '\033]11;grey42\007' '#6b6b6b' || exit 1 +do_test '\033]11;grey43\007' '#6e6e6e' || exit 1 +do_test '\033]11;grey44\007' '#707070' || exit 1 +do_test '\033]11;grey45\007' '#737373' || exit 1 +do_test '\033]11;grey46\007' '#757575' || exit 1 +do_test '\033]11;grey47\007' '#787878' || exit 1 +do_test '\033]11;grey48\007' '#7a7a7a' || exit 1 +do_test '\033]11;grey49\007' '#7d7d7d' || exit 1 +do_test '\033]11;grey50\007' '#7f7f7f' || exit 1 +do_test '\033]11;grey51\007' '#828282' || exit 1 +do_test '\033]11;grey52\007' '#858585' || exit 1 +do_test '\033]11;grey53\007' '#878787' || exit 1 +do_test '\033]11;grey54\007' '#8a8a8a' || exit 1 +do_test '\033]11;grey55\007' '#8c8c8c' || exit 1 +do_test '\033]11;grey56\007' '#8f8f8f' || exit 1 +do_test '\033]11;grey57\007' '#919191' || exit 1 +do_test '\033]11;grey58\007' '#949494' || exit 1 +do_test '\033]11;grey59\007' '#969696' || exit 1 +do_test '\033]11;grey60\007' '#999999' || exit 1 +do_test '\033]11;grey61\007' '#9c9c9c' || exit 1 +do_test '\033]11;grey62\007' '#9e9e9e' || exit 1 +do_test '\033]11;grey63\007' '#a1a1a1' || exit 1 +do_test '\033]11;grey64\007' '#a3a3a3' || exit 1 +do_test '\033]11;grey65\007' '#a6a6a6' || exit 1 +do_test '\033]11;grey66\007' '#a8a8a8' || exit 1 +do_test '\033]11;grey67\007' '#ababab' || exit 1 +do_test '\033]11;grey68\007' '#adadad' || exit 1 +do_test '\033]11;grey69\007' '#b0b0b0' || exit 1 +do_test '\033]11;grey70\007' '#b3b3b3' || exit 1 +do_test '\033]11;grey71\007' '#b5b5b5' || exit 1 +do_test '\033]11;grey72\007' '#b8b8b8' || exit 1 +do_test '\033]11;grey73\007' '#bababa' || exit 1 +do_test '\033]11;grey74\007' '#bdbdbd' || exit 1 +do_test '\033]11;grey75\007' '#bfbfbf' || exit 1 +do_test '\033]11;grey76\007' '#c2c2c2' || exit 1 +do_test '\033]11;grey77\007' '#c4c4c4' || exit 1 +do_test '\033]11;grey78\007' '#c7c7c7' || exit 1 +do_test '\033]11;grey79\007' '#c9c9c9' || exit 1 +do_test '\033]11;grey80\007' '#cccccc' || exit 1 +do_test '\033]11;grey81\007' '#cfcfcf' || exit 1 +do_test '\033]11;grey82\007' '#d1d1d1' || exit 1 +do_test '\033]11;grey83\007' '#d4d4d4' || exit 1 +do_test '\033]11;grey84\007' '#d6d6d6' || exit 1 +do_test '\033]11;grey85\007' '#d9d9d9' || exit 1 +do_test '\033]11;grey86\007' '#dbdbdb' || exit 1 +do_test '\033]11;grey87\007' '#dedede' || exit 1 +do_test '\033]11;grey88\007' '#e0e0e0' || exit 1 +do_test '\033]11;grey89\007' '#e3e3e3' || exit 1 +do_test '\033]11;grey90\007' '#e5e5e5' || exit 1 +do_test '\033]11;grey91\007' '#e8e8e8' || exit 1 +do_test '\033]11;grey92\007' '#ebebeb' || exit 1 +do_test '\033]11;grey93\007' '#ededed' || exit 1 +do_test '\033]11;grey94\007' '#f0f0f0' || exit 1 +do_test '\033]11;grey95\007' '#f2f2f2' || exit 1 +do_test '\033]11;grey96\007' '#f5f5f5' || exit 1 +do_test '\033]11;grey97\007' '#f7f7f7' || exit 1 +do_test '\033]11;grey98\007' '#fafafa' || exit 1 +do_test '\033]11;grey99\007' '#fcfcfc' || exit 1 +do_test '\033]11;grey100\007' '#ffffff' || exit 1 + +do_test '\033]11;gray\007' '#bebebe' || exit 1 +do_test '\033]11;gray0\007' '#000000' || exit 1 +do_test '\033]11;gray1\007' '#030303' || exit 1 +do_test '\033]11;gray2\007' '#050505' || exit 1 +do_test '\033]11;gray3\007' '#080808' || exit 1 +do_test '\033]11;gray4\007' '#0a0a0a' || exit 1 +do_test '\033]11;gray5\007' '#0d0d0d' || exit 1 +do_test '\033]11;gray6\007' '#0f0f0f' || exit 1 +do_test '\033]11;gray7\007' '#121212' || exit 1 +do_test '\033]11;gray8\007' '#141414' || exit 1 +do_test '\033]11;gray9\007' '#171717' || exit 1 +do_test '\033]11;gray10\007' '#1a1a1a' || exit 1 +do_test '\033]11;gray11\007' '#1c1c1c' || exit 1 +do_test '\033]11;gray12\007' '#1f1f1f' || exit 1 +do_test '\033]11;gray13\007' '#212121' || exit 1 +do_test '\033]11;gray14\007' '#242424' || exit 1 +do_test '\033]11;gray15\007' '#262626' || exit 1 +do_test '\033]11;gray16\007' '#292929' || exit 1 +do_test '\033]11;gray17\007' '#2b2b2b' || exit 1 +do_test '\033]11;gray18\007' '#2e2e2e' || exit 1 +do_test '\033]11;gray19\007' '#303030' || exit 1 +do_test '\033]11;gray20\007' '#333333' || exit 1 +do_test '\033]11;gray21\007' '#363636' || exit 1 +do_test '\033]11;gray22\007' '#383838' || exit 1 +do_test '\033]11;gray23\007' '#3b3b3b' || exit 1 +do_test '\033]11;gray24\007' '#3d3d3d' || exit 1 +do_test '\033]11;gray25\007' '#404040' || exit 1 +do_test '\033]11;gray26\007' '#424242' || exit 1 +do_test '\033]11;gray27\007' '#454545' || exit 1 +do_test '\033]11;gray28\007' '#474747' || exit 1 +do_test '\033]11;gray29\007' '#4a4a4a' || exit 1 +do_test '\033]11;gray30\007' '#4d4d4d' || exit 1 +do_test '\033]11;gray31\007' '#4f4f4f' || exit 1 +do_test '\033]11;gray32\007' '#525252' || exit 1 +do_test '\033]11;gray33\007' '#545454' || exit 1 +do_test '\033]11;gray34\007' '#575757' || exit 1 +do_test '\033]11;gray35\007' '#595959' || exit 1 +do_test '\033]11;gray36\007' '#5c5c5c' || exit 1 +do_test '\033]11;gray37\007' '#5e5e5e' || exit 1 +do_test '\033]11;gray38\007' '#616161' || exit 1 +do_test '\033]11;gray39\007' '#636363' || exit 1 +do_test '\033]11;gray40\007' '#666666' || exit 1 +do_test '\033]11;gray41\007' '#696969' || exit 1 +do_test '\033]11;gray42\007' '#6b6b6b' || exit 1 +do_test '\033]11;gray43\007' '#6e6e6e' || exit 1 +do_test '\033]11;gray44\007' '#707070' || exit 1 +do_test '\033]11;gray45\007' '#737373' || exit 1 +do_test '\033]11;gray46\007' '#757575' || exit 1 +do_test '\033]11;gray47\007' '#787878' || exit 1 +do_test '\033]11;gray48\007' '#7a7a7a' || exit 1 +do_test '\033]11;gray49\007' '#7d7d7d' || exit 1 +do_test '\033]11;gray50\007' '#7f7f7f' || exit 1 +do_test '\033]11;gray51\007' '#828282' || exit 1 +do_test '\033]11;gray52\007' '#858585' || exit 1 +do_test '\033]11;gray53\007' '#878787' || exit 1 +do_test '\033]11;gray54\007' '#8a8a8a' || exit 1 +do_test '\033]11;gray55\007' '#8c8c8c' || exit 1 +do_test '\033]11;gray56\007' '#8f8f8f' || exit 1 +do_test '\033]11;gray57\007' '#919191' || exit 1 +do_test '\033]11;gray58\007' '#949494' || exit 1 +do_test '\033]11;gray59\007' '#969696' || exit 1 +do_test '\033]11;gray60\007' '#999999' || exit 1 +do_test '\033]11;gray61\007' '#9c9c9c' || exit 1 +do_test '\033]11;gray62\007' '#9e9e9e' || exit 1 +do_test '\033]11;gray63\007' '#a1a1a1' || exit 1 +do_test '\033]11;gray64\007' '#a3a3a3' || exit 1 +do_test '\033]11;gray65\007' '#a6a6a6' || exit 1 +do_test '\033]11;gray66\007' '#a8a8a8' || exit 1 +do_test '\033]11;gray67\007' '#ababab' || exit 1 +do_test '\033]11;gray68\007' '#adadad' || exit 1 +do_test '\033]11;gray69\007' '#b0b0b0' || exit 1 +do_test '\033]11;gray70\007' '#b3b3b3' || exit 1 +do_test '\033]11;gray71\007' '#b5b5b5' || exit 1 +do_test '\033]11;gray72\007' '#b8b8b8' || exit 1 +do_test '\033]11;gray73\007' '#bababa' || exit 1 +do_test '\033]11;gray74\007' '#bdbdbd' || exit 1 +do_test '\033]11;gray75\007' '#bfbfbf' || exit 1 +do_test '\033]11;gray76\007' '#c2c2c2' || exit 1 +do_test '\033]11;gray77\007' '#c4c4c4' || exit 1 +do_test '\033]11;gray78\007' '#c7c7c7' || exit 1 +do_test '\033]11;gray79\007' '#c9c9c9' || exit 1 +do_test '\033]11;gray80\007' '#cccccc' || exit 1 +do_test '\033]11;gray81\007' '#cfcfcf' || exit 1 +do_test '\033]11;gray82\007' '#d1d1d1' || exit 1 +do_test '\033]11;gray83\007' '#d4d4d4' || exit 1 +do_test '\033]11;gray84\007' '#d6d6d6' || exit 1 +do_test '\033]11;gray85\007' '#d9d9d9' || exit 1 +do_test '\033]11;gray86\007' '#dbdbdb' || exit 1 +do_test '\033]11;gray87\007' '#dedede' || exit 1 +do_test '\033]11;gray88\007' '#e0e0e0' || exit 1 +do_test '\033]11;gray89\007' '#e3e3e3' || exit 1 +do_test '\033]11;gray90\007' '#e5e5e5' || exit 1 +do_test '\033]11;gray91\007' '#e8e8e8' || exit 1 +do_test '\033]11;gray92\007' '#ebebeb' || exit 1 +do_test '\033]11;gray93\007' '#ededed' || exit 1 +do_test '\033]11;gray94\007' '#f0f0f0' || exit 1 +do_test '\033]11;gray95\007' '#f2f2f2' || exit 1 +do_test '\033]11;gray96\007' '#f5f5f5' || exit 1 +do_test '\033]11;gray97\007' '#f7f7f7' || exit 1 +do_test '\033]11;gray98\007' '#fafafa' || exit 1 +do_test '\033]11;gray99\007' '#fcfcfc' || exit 1 +do_test '\033]11;gray100\007' '#ffffff' || exit 1 + +$TMUX -f/dev/null kill-server 2>/dev/null +exit 0 From d768fc2553c2bdec6bb7b026ffffdaee0dd102f4 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 15 Feb 2021 14:22:35 +0000 Subject: [PATCH 71/90] Make SGR 6 (rapid blink) the same as SGR 5 (blink) and make SGR 21 to the same as SGR 4:2, it is an old alternative. GitHub issue 2567. --- input.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/input.c b/input.c index d22ee1a8..590a157d 100644 --- a/input.c +++ b/input.c @@ -2101,6 +2101,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) gc->attr |= GRID_ATTR_UNDERSCORE; break; case 5: + case 6: gc->attr |= GRID_ATTR_BLINK; break; case 7: @@ -2112,6 +2113,10 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) case 9: gc->attr |= GRID_ATTR_STRIKETHROUGH; break; + case 21: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_2; + break; case 22: gc->attr &= ~(GRID_ATTR_BRIGHT|GRID_ATTR_DIM); break; From af3ffa9c41936078d27b5ba1f96cec67850f98cb Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Feb 2021 07:18:36 +0000 Subject: [PATCH 72/90] Move the call to setupterm() into the client and have it pass the results to the server over imsg, means the server does not need to enter ncurses or read terminfo db. Old clients will not work with a new server. --- client.c | 46 ++++++++++---- server-client.c | 20 ++++-- tmux.h | 9 ++- tty-term.c | 162 +++++++++++++++++++++++++++++++++--------------- tty.c | 4 +- 5 files changed, 169 insertions(+), 72 deletions(-) diff --git a/client.c b/client.c index 5a454ffe..002cc6c7 100644 --- a/client.c +++ b/client.c @@ -62,7 +62,8 @@ static __dead void client_exec(const char *,const char *); static int client_get_lock(char *); static int client_connect(struct event_base *, const char *, uint64_t); -static void client_send_identify(const char *, const char *, int); +static void client_send_identify(const char *, const char *, + char **, u_int, const char *, int); static void client_signal(int); static void client_dispatch(struct imsg *, void *); static void client_dispatch_attached(struct imsg *); @@ -235,13 +236,14 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, struct cmd_parse_result *pr; struct msg_command *data; int fd, i; - const char *ttynam, *cwd; + const char *ttynam, *termname, *cwd; pid_t ppid; enum msgtype msg; struct termios tio, saved_tio; size_t size, linesize = 0; ssize_t linelen; - char *line = NULL; + char *line = NULL, **caps = NULL, *cause; + u_int ncaps = 0; /* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ signal(SIGCHLD, SIG_IGN); @@ -297,6 +299,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, cwd = "/"; if ((ttynam = ttyname(STDIN_FILENO)) == NULL) ttynam = ""; + if ((termname = getenv("TERM")) == NULL) + termname = ""; /* * Drop privileges for client. "proc exec" is needed for -c and for @@ -312,6 +316,16 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, NULL) != 0) fatal("pledge failed"); + /* Load terminfo entry if any. */ + if (isatty(STDIN_FILENO) && + *termname != '\0' && + tty_term_read_list(termname, STDIN_FILENO, &caps, &ncaps, + &cause) != 0) { + fprintf(stderr, "%s\n", cause); + free(cause); + return (1); + } + /* Free stuff that is not used in the client. */ if (ptm_fd != -1) close(ptm_fd); @@ -340,7 +354,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, } /* Send identify messages. */ - client_send_identify(ttynam, cwd, feat); + client_send_identify(ttynam, termname, caps, ncaps, cwd, feat); + tty_term_free_list(caps, ncaps); /* Send first command. */ if (msg == MSG_COMMAND) { @@ -423,27 +438,32 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, /* Send identify messages to server. */ static void -client_send_identify(const char *ttynam, const char *cwd, int feat) +client_send_identify(const char *ttynam, const char *termname, char **caps, + u_int ncaps, const char *cwd, int feat) { - const char *s; - char **ss; - size_t sslen; - int fd, flags = client_flags; - pid_t pid; + char **ss; + size_t sslen; + int fd, flags = client_flags; + pid_t pid; + u_int i; proc_send(client_peer, MSG_IDENTIFY_FLAGS, -1, &flags, sizeof flags); proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &client_flags, sizeof client_flags); - if ((s = getenv("TERM")) == NULL) - s = ""; - proc_send(client_peer, MSG_IDENTIFY_TERM, -1, s, strlen(s) + 1); + proc_send(client_peer, MSG_IDENTIFY_TERM, -1, termname, + strlen(termname) + 1); proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat); proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam, strlen(ttynam) + 1); proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1); + for (i = 0; i < ncaps; i++) { + proc_send(client_peer, MSG_IDENTIFY_TERMINFO, -1, + caps[i], strlen(caps[i]) + 1); + } + if ((fd = dup(STDIN_FILENO)) == -1) fatal("dup failed"); proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0); diff --git a/server-client.c b/server-client.c index 7d9cc0e6..adeea5dd 100644 --- a/server-client.c +++ b/server-client.c @@ -307,6 +307,7 @@ server_client_lost(struct client *c) free(c->term_name); free(c->term_type); + tty_term_free_list(c->term_caps, c->term_ncaps); status_free(c); @@ -1997,16 +1998,17 @@ server_client_dispatch(struct imsg *imsg, void *arg) datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { + case MSG_IDENTIFY_CLIENTPID: + case MSG_IDENTIFY_CWD: + case MSG_IDENTIFY_ENVIRON: case MSG_IDENTIFY_FEATURES: case MSG_IDENTIFY_FLAGS: case MSG_IDENTIFY_LONGFLAGS: - case MSG_IDENTIFY_TERM: - case MSG_IDENTIFY_TTYNAME: - case MSG_IDENTIFY_CWD: case MSG_IDENTIFY_STDIN: case MSG_IDENTIFY_STDOUT: - case MSG_IDENTIFY_ENVIRON: - case MSG_IDENTIFY_CLIENTPID: + case MSG_IDENTIFY_TERM: + case MSG_IDENTIFY_TERMINFO: + case MSG_IDENTIFY_TTYNAME: case MSG_IDENTIFY_DONE: server_client_dispatch_identify(c, imsg); break; @@ -2200,6 +2202,14 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) c->term_name = xstrdup(data); log_debug("client %p IDENTIFY_TERM %s", c, data); break; + case MSG_IDENTIFY_TERMINFO: + if (datalen == 0 || data[datalen - 1] != '\0') + fatalx("bad MSG_IDENTIFY_TERMINFO string"); + c->term_caps = xreallocarray(c->term_caps, c->term_ncaps + 1, + sizeof *c->term_caps); + c->term_caps[c->term_ncaps++] = xstrdup(data); + log_debug("client %p IDENTIFY_TERMINFO %s", c, data); + break; case MSG_IDENTIFY_TTYNAME: if (datalen == 0 || data[datalen - 1] != '\0') fatalx("bad MSG_IDENTIFY_TTYNAME string"); diff --git a/tmux.h b/tmux.h index 18921f6e..88c6d30e 100644 --- a/tmux.h +++ b/tmux.h @@ -500,6 +500,7 @@ enum msgtype { MSG_IDENTIFY_FEATURES, MSG_IDENTIFY_STDOUT, MSG_IDENTIFY_LONGFLAGS, + MSG_IDENTIFY_TERMINFO, MSG_COMMAND = 200, MSG_DETACH, @@ -1603,6 +1604,8 @@ struct client { char *term_name; int term_features; char *term_type; + char **term_caps; + u_int term_ncaps; char *ttyname; struct tty tty; @@ -2167,8 +2170,12 @@ extern struct tty_terms tty_terms; u_int tty_term_ncodes(void); void tty_term_apply(struct tty_term *, const char *, int); void tty_term_apply_overrides(struct tty_term *); -struct tty_term *tty_term_create(struct tty *, char *, int *, int, char **); +struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, int *, + char **); void tty_term_free(struct tty_term *); +int tty_term_read_list(const char *, int, char ***, u_int *, + char **); +void tty_term_free_list(char **, u_int); int tty_term_has(struct tty_term *, enum tty_code_code); const char *tty_term_string(struct tty_term *, enum tty_code_code); const char *tty_term_string1(struct tty_term *, enum tty_code_code, int); diff --git a/tty-term.c b/tty-term.c index b989328c..c8048f77 100644 --- a/tty-term.c +++ b/tty-term.c @@ -450,7 +450,8 @@ tty_term_apply_overrides(struct tty_term *term) } struct tty_term * -tty_term_create(struct tty *tty, char *name, int *feat, int fd, char **cause) +tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps, + int *feat, char **cause) { struct tty_term *term; const struct tty_term_code_entry *ent; @@ -458,10 +459,9 @@ tty_term_create(struct tty *tty, char *name, int *feat, int fd, char **cause) struct options_entry *o; struct options_array_item *a; union options_value *ov; - u_int i; - int n, error; - const char *s, *acs; - size_t offset; + u_int i, j; + const char *s, *acs, *value; + size_t offset, namelen; char *first; log_debug("adding term %s", name); @@ -472,57 +472,38 @@ tty_term_create(struct tty *tty, char *name, int *feat, int fd, char **cause) term->codes = xcalloc(tty_term_ncodes(), sizeof *term->codes); LIST_INSERT_HEAD(&tty_terms, term, entry); - /* Set up curses terminal. */ - if (setupterm(name, fd, &error) != OK) { - switch (error) { - case 1: - xasprintf(cause, "can't use hardcopy terminal: %s", - name); - break; - case 0: - xasprintf(cause, "missing or unsuitable terminal: %s", - name); - break; - case -1: - xasprintf(cause, "can't find terminfo database"); - break; - default: - xasprintf(cause, "unknown error"); - break; - } - goto error; - } - /* Fill in codes. */ - for (i = 0; i < tty_term_ncodes(); i++) { - ent = &tty_term_codes[i]; + for (i = 0; i < ncaps; i++) { + namelen = strcspn(caps[i], "="); + if (namelen == 0) + continue; + value = caps[i] + namelen + 1; - code = &term->codes[i]; - code->type = TTYCODE_NONE; - switch (ent->type) { - case TTYCODE_NONE: - break; - case TTYCODE_STRING: - s = tigetstr((char *) ent->name); - if (s == NULL || s == (char *) -1) + for (j = 0; j < tty_term_ncodes(); j++) { + ent = &tty_term_codes[j]; + if (strncmp(ent->name, caps[i], namelen) != 0) + continue; + if (ent->name[namelen] != '\0') + continue; + + code = &term->codes[j]; + code->type = TTYCODE_NONE; + switch (ent->type) { + case TTYCODE_NONE: break; - code->type = TTYCODE_STRING; - code->value.string = tty_term_strip(s); - break; - case TTYCODE_NUMBER: - n = tigetnum((char *) ent->name); - if (n == -1 || n == -2) + case TTYCODE_STRING: + code->type = TTYCODE_STRING; + code->value.string = tty_term_strip(value); break; - code->type = TTYCODE_NUMBER; - code->value.number = n; - break; - case TTYCODE_FLAG: - n = tigetflag((char *) ent->name); - if (n == -1) + case TTYCODE_NUMBER: + code->type = TTYCODE_NUMBER; + code->value.number = atoi(value); break; - code->type = TTYCODE_FLAG; - code->value.flag = n; - break; + case TTYCODE_FLAG: + code->type = TTYCODE_FLAG; + code->value.flag = (*value == '1'); + break; + } } } @@ -643,6 +624,85 @@ tty_term_free(struct tty_term *term) free(term); } +int +tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps, + char **cause) +{ + const struct tty_term_code_entry *ent; + int error, n; + u_int i; + const char *s; + char tmp[11]; + + if (setupterm(name, fd, &error) != OK) { + switch (error) { + case 1: + xasprintf(cause, "can't use hardcopy terminal: %s", + name); + break; + case 0: + xasprintf(cause, "missing or unsuitable terminal: %s", + name); + break; + case -1: + xasprintf(cause, "can't find terminfo database"); + break; + default: + xasprintf(cause, "unknown error"); + break; + } + return (-1); + } + + *ncaps = 0; + *caps = NULL; + + for (i = 0; i < tty_term_ncodes(); i++) { + ent = &tty_term_codes[i]; + switch (ent->type) { + case TTYCODE_NONE: + break; + case TTYCODE_STRING: + s = tigetstr((char *)ent->name); + if (s == NULL || s == (char *)-1) + continue; + break; + case TTYCODE_NUMBER: + n = tigetnum((char *)ent->name); + if (n == -1 || n == -2) + continue; + xsnprintf(tmp, sizeof tmp, "%d", n); + s = tmp; + break; + case TTYCODE_FLAG: + n = tigetflag((char *) ent->name); + if (n == -1) + continue; + if (n) + s = "1"; + else + s = "0"; + break; + } + *caps = xreallocarray(*caps, (*ncaps) + 1, sizeof **caps); + xasprintf(&(*caps)[*ncaps], "%s=%s", ent->name, s); + (*ncaps)++; + } + + del_curterm(cur_term); + return (0); +} + +void +tty_term_free_list(char **caps, u_int ncaps) +{ + u_int i; + + for (i = 0; i < ncaps; i++) + free(caps[i]); + free(caps); +} + int tty_term_has(struct tty_term *term, enum tty_code_code code) { diff --git a/tty.c b/tty.c index 279bafaf..399bbae8 100644 --- a/tty.c +++ b/tty.c @@ -249,8 +249,8 @@ tty_open(struct tty *tty, char **cause) { struct client *c = tty->client; - tty->term = tty_term_create(tty, c->term_name, &c->term_features, - c->fd, cause); + tty->term = tty_term_create(tty, c->term_name, c->term_caps, + c->term_ncaps, &c->term_features, cause); if (tty->term == NULL) { tty_close(tty); return (-1); From fb42ae3071763fdc6a2fe2e57feec774623b1c33 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 18 Feb 2021 13:30:24 +0000 Subject: [PATCH 73/90] Reduce len when moving past spaces in OSC 11 parameter. --- input.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/input.c b/input.c index 590a157d..f6aeb027 100644 --- a/input.c +++ b/input.c @@ -2460,8 +2460,10 @@ input_osc_parse_colour(const char *p) (1 - m) * (1 - k) * 255, (1 - y) * (1 - k) * 255); } else { - while (*p == ' ') + while (len != 0 && *p == ' ') { p++; + len--; + } while (len != 0 && p[len - 1] == ' ') len--; copy = xstrndup(p, len); From b04f8acb7057bda74e30976acedbbd73767e5bdc Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 19 Feb 2021 09:09:16 +0000 Subject: [PATCH 74/90] Check return value of chdir() to stop a silly warning with some compilers, GitHub issue 2573. --- job.c | 8 ++++---- spawn.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/job.c b/job.c index efc0199e..6267336b 100644 --- a/job.c +++ b/job.c @@ -114,10 +114,10 @@ job_run(const char *cmd, struct session *s, const char *cwd, proc_clear_signals(server_proc, 1); sigprocmask(SIG_SETMASK, &oldset, NULL); - if (cwd == NULL || chdir(cwd) != 0) { - if ((home = find_home()) == NULL || chdir(home) != 0) - chdir("/"); - } + if ((cwd == NULL || chdir(cwd) != 0) && + ((home = find_home()) == NULL || chdir(home) != 0) && + chdir("/") != 0) + fatal("chdir failed"); environ_push(env); environ_free(env); diff --git a/spawn.c b/spawn.c index d5b52ffa..41ffe612 100644 --- a/spawn.c +++ b/spawn.c @@ -379,10 +379,10 @@ spawn_pane(struct spawn_context *sc, char **cause) * Child process. Change to the working directory or home if that * fails. */ - if (chdir(new_wp->cwd) != 0) { - if ((tmp = find_home()) == NULL || chdir(tmp) != 0) - chdir("/"); - } + if (chdir(new_wp->cwd) != 0 && + ((tmp = find_home()) == NULL || chdir(tmp) != 0) && + chdir("/") != 0) + fatal("chdir failed"); /* * Update terminal escape characters from the session if available and From 8986c8dfcd0083e5c767b8a247c119a25e1f8093 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Feb 2021 06:53:04 +0000 Subject: [PATCH 75/90] Move jump commands to grid reader, make them UTF-8 aware, and tidy up, from Anindya Mukherjee. --- grid-reader.c | 62 +++++++++ status.c | 12 +- tmux.h | 4 + window-copy.c | 344 +++++++++++++++++++------------------------------- 4 files changed, 206 insertions(+), 216 deletions(-) diff --git a/grid-reader.c b/grid-reader.c index a1af3aaa..c011ea1d 100644 --- a/grid-reader.c +++ b/grid-reader.c @@ -17,6 +17,7 @@ */ #include "tmux.h" +#include /* Initialise virtual cursor. */ void @@ -301,3 +302,64 @@ grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, gr->cx = oldx; gr->cy = oldy; } + +/* Jump forward to character. */ +int +grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) +{ + struct grid_cell gc; + u_int px, py, xx, yy; + + px = gr->cx; + yy = gr->gd->hsize + gr->gd->sy - 1; + + for (py = gr->cy; py <= yy; py++) { + xx = grid_line_length(gr->gd, py); + while (px < xx) { + grid_get_cell(gr->gd, px, py, &gc); + if (!(gc.flags & GRID_FLAG_PADDING) && + gc.data.size == jc->size && + memcmp(gc.data.data, jc->data, gc.data.size) == 0) { + gr->cx = px; + gr->cy = py; + return 1; + } + px++; + } + + if (py == yy || + !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) + return 0; + px = 0; + } + return 0; +} + +/* Jump back to character. */ +int +grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) +{ + struct grid_cell gc; + u_int px, py, xx; + + xx = gr->cx + 1; + + for (py = gr->cy + 1; py > 0; py--) { + for (px = xx; px > 0; px--) { + grid_get_cell(gr->gd, px - 1, py - 1, &gc); + if (!(gc.flags & GRID_FLAG_PADDING) && + gc.data.size == jc->size && + memcmp(gc.data.data, jc->data, gc.data.size) == 0) { + gr->cx = px - 1; + gr->cy = py - 1; + return 1; + } + } + + if (py == 1 || + !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) + return 0; + xx = grid_line_length(gr->gd, py - 2); + } + return 0; +} diff --git a/status.c b/status.c index 82107c58..154d9452 100644 --- a/status.c +++ b/status.c @@ -1319,12 +1319,14 @@ append_key: } if (c->prompt_flags & PROMPT_SINGLE) { - s = utf8_tocstr(c->prompt_buffer); - if (strlen(s) != 1) + if (utf8_strlen(c->prompt_buffer) != 1) status_prompt_clear(c); - else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) - status_prompt_clear(c); - free(s); + else { + s = utf8_tocstr(c->prompt_buffer); + if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) + status_prompt_clear(c); + free(s); + } } changed: diff --git a/tmux.h b/tmux.h index 88c6d30e..4339f6a7 100644 --- a/tmux.h +++ b/tmux.h @@ -2589,6 +2589,10 @@ void grid_reader_cursor_next_word(struct grid_reader *, const char *); void grid_reader_cursor_next_word_end(struct grid_reader *, const char *); void grid_reader_cursor_previous_word(struct grid_reader *, const char *, int); +int grid_reader_cursor_jump(struct grid_reader *, + const struct utf8_data *); +int grid_reader_cursor_jump_back(struct grid_reader *, + const struct utf8_data *); /* grid-view.c */ void grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *); diff --git a/window-copy.c b/window-copy.c index c6b25413..af8d2937 100644 --- a/window-copy.c +++ b/window-copy.c @@ -135,6 +135,10 @@ static void window_copy_move_mouse(struct mouse_event *); static void window_copy_drag_update(struct client *, struct mouse_event *); static void window_copy_drag_release(struct client *, struct mouse_event *); static void window_copy_jump_to_mark(struct window_mode_entry *); +static void window_copy_acquire_cursor_up(struct window_mode_entry *, + u_int, u_int, u_int, u_int, u_int); +static void window_copy_acquire_cursor_down(struct window_mode_entry *, + u_int, u_int, u_int, u_int, u_int, u_int, int); const struct window_mode window_copy_mode = { .name = "copy-mode", @@ -284,8 +288,8 @@ struct window_copy_mode_data { #define WINDOW_COPY_SEARCH_TIMEOUT 10000 #define WINDOW_COPY_SEARCH_ALL_TIMEOUT 200 - int jumptype; - char jumpchar; + int jumptype; + struct utf8_data *jumpchar; struct event dragtimer; #define WINDOW_COPY_DRAG_REPEAT_TIME 50000 @@ -401,7 +405,7 @@ window_copy_common_init(struct window_mode_entry *wme) data->searchall = 1; data->jumptype = WINDOW_COPY_OFF; - data->jumpchar = '\0'; + data->jumpchar = NULL; screen_init(&data->screen, screen_size_x(base), screen_size_y(base), 0); data->modekeys = options_get_number(wp->window->options, "mode-keys"); @@ -482,6 +486,7 @@ window_copy_free(struct window_mode_entry *wme) free(data->searchmark); free(data->searchstr); + free(data->jumpchar); screen_free(data->backing); free(data->backing); @@ -1935,7 +1940,8 @@ window_copy_cmd_jump_backward(struct window_copy_cmd_state *cs) if (*argument != '\0') { data->jumptype = WINDOW_COPY_JUMPBACKWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(argument); for (; np != 0; np--) window_copy_cursor_jump_back(wme); } @@ -1952,7 +1958,8 @@ window_copy_cmd_jump_forward(struct window_copy_cmd_state *cs) if (*argument != '\0') { data->jumptype = WINDOW_COPY_JUMPFORWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(argument); for (; np != 0; np--) window_copy_cursor_jump(wme); } @@ -1969,7 +1976,8 @@ window_copy_cmd_jump_to_backward(struct window_copy_cmd_state *cs) if (*argument != '\0') { data->jumptype = WINDOW_COPY_JUMPTOBACKWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(argument); for (; np != 0; np--) window_copy_cursor_jump_to_back(wme); } @@ -1986,7 +1994,8 @@ window_copy_cmd_jump_to_forward(struct window_copy_cmd_state *cs) if (*argument != '\0') { data->jumptype = WINDOW_COPY_JUMPTOFORWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(argument); for (; np != 0; np--) window_copy_cursor_jump_to(wme); } @@ -4074,7 +4083,7 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, oldy, yy, ny, nd, hsize; + u_int px, py, oldy, hsize; px = data->cx; hsize = screen_hsize(back_s); @@ -4084,25 +4093,7 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_start_of_line(&gr, 1); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll up if we went off the visible screen. */ - yy = hsize - data->oy; - if (py < yy) { - ny = yy - py; - cy = 0; - nd = 1; - } else { - ny = 0; - cy = py - yy; - nd = oldy - cy + 1; - } - while (ny > 0) { - window_copy_cursor_up(wme, 1); - ny--; - } - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, nd); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void @@ -4134,7 +4125,7 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, oldy, yy, ny, nd, hsize; + u_int px, py, oldy, hsize; px = data->cx; hsize = screen_hsize(back_s); @@ -4147,28 +4138,8 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) else grid_reader_cursor_end_of_line(&gr, 1, 0); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll down if we went off the visible screen. */ - cy = py - hsize + data->oy; - yy = screen_size_y(back_s) - 1; - if (cy > yy) { - ny = cy - yy; - oldy = yy; - nd = 1; - } else { - ny = 0; - nd = cy - oldy + 1; - } - while (ny > 0) { - window_copy_cursor_down(wme, 1); - ny--; - } - if (cy > yy) - window_copy_update_cursor(wme, px, yy); - else - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, oldy, nd); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, 0); } static void @@ -4228,32 +4199,17 @@ window_copy_cursor_left(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, yy, ny, hsize; + u_int px, py, oldy, hsize; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; + oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_left(&gr); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll up if we went off the visible screen. */ - yy = hsize - data->oy; - if (py < yy) { - ny = yy - py; - cy = 0; - } else { - ny = 0; - cy = py - yy; - } - while (ny > 0) { - window_copy_cursor_up(wme, 1); - ny--; - } - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void @@ -4262,33 +4218,18 @@ window_copy_cursor_right(struct window_mode_entry *wme, int all) struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, yy, ny, hsize; + u_int px, py, oldy, hsize; px = data->cx; hsize = screen_hsize(back_s); py = hsize + data->cy - data->oy; + oldy = data->cy; grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_right(&gr, 1, all); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll down if we went off the visible screen. */ - cy = py - hsize + data->oy; - yy = screen_size_y(back_s) - 1; - if (cy > yy) - ny = cy - yy; - else - ny = 0; - while (ny > 0) { - window_copy_cursor_down(wme, 1); - ny--; - } - if (cy > yy) - window_copy_update_cursor(wme, px, yy); - else - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, 0); } static void @@ -4422,23 +4363,19 @@ window_copy_cursor_jump(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py, xx; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx + 1; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - while (px < xx) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - px++; + grid_reader_start(&gr, back_s->grid, px, py); + if (grid_reader_cursor_jump(&gr, data->jumpchar)) { + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, + screen_size_y(back_s), data->oy, oldy, px, py, 0); } } @@ -4447,27 +4384,22 @@ window_copy_cursor_jump_back(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; if (px > 0) px--; - for (;;) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - if (px == 0) - break; - px--; + grid_reader_start(&gr, back_s->grid, px, py); + if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) { + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, + py); } } @@ -4476,23 +4408,20 @@ window_copy_cursor_jump_to(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py, xx; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx + 2; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - while (px < xx) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px - 1, data->cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - px++; + grid_reader_start(&gr, back_s->grid, px, py); + if (grid_reader_cursor_jump(&gr, data->jumpchar)) { + grid_reader_cursor_left(&gr); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, + screen_size_y(back_s), data->oy, oldy, px, py, 0); } } @@ -4501,11 +4430,13 @@ window_copy_cursor_jump_to_back(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; if (px > 0) px--; @@ -4513,18 +4444,12 @@ window_copy_cursor_jump_to_back(struct window_mode_entry *wme) if (px > 0) px--; - for (;;) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px + 1, data->cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - if (px == 0) - break; - px--; + grid_reader_start(&gr, back_s->grid, px, py); + if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) { + grid_reader_cursor_right(&gr, 1, 0); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, + py); } } @@ -4535,7 +4460,7 @@ window_copy_cursor_next_word(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, oldy, yy, ny, nd, hsize; + u_int px, py, oldy, hsize; px = data->cx; hsize = screen_hsize(back_s); @@ -4545,28 +4470,8 @@ window_copy_cursor_next_word(struct window_mode_entry *wme, grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_next_word(&gr, separators); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll down if we went off the visible screen. */ - cy = py - hsize + data->oy; - yy = screen_size_y(back_s) - 1; - if (cy > yy) { - ny = cy - yy; - oldy = yy; - nd = 1; - } else { - ny = 0; - nd = cy - oldy + 1; - } - while (ny > 0) { - window_copy_cursor_down(wme, 1); - ny--; - } - if (cy > yy) - window_copy_update_cursor(wme, px, yy); - else - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, oldy, nd); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, 0); } static void @@ -4627,7 +4532,7 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, struct options *oo = wp->window->options; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, oldy, yy, ny, nd, hsize; + u_int px, py, oldy, hsize; int keys; px = data->cx; @@ -4643,28 +4548,8 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme, if (keys == MODEKEY_VI) grid_reader_cursor_left(&gr); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll down if we went off the visible screen. */ - cy = py - hsize + data->oy; - yy = screen_size_y(back_s) - 1; - if (cy > yy) { - ny = cy - yy; - oldy = yy; - nd = 1; - } else { - ny = 0; - nd = cy - oldy + 1; - } - while (ny > 0) { - window_copy_cursor_down(wme, 1); - ny--; - } - if (cy > yy) - window_copy_update_cursor(wme, px, yy); - else - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, no_reset)) - window_copy_redraw_lines(wme, oldy, nd); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, no_reset); } /* Compute the previous place where a word begins. */ @@ -4721,7 +4606,7 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_reader gr; - u_int px, py, cy, oldy, yy, ny, nd, hsize; + u_int px, py, oldy, hsize; px = data->cx; hsize = screen_hsize(back_s); @@ -4731,25 +4616,7 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, grid_reader_start(&gr, back_s->grid, px, py); grid_reader_cursor_previous_word(&gr, separators, already); grid_reader_get_cursor(&gr, &px, &py); - - /* Scroll up if we went off the visible screen. */ - yy = hsize - data->oy; - if (py < yy) { - ny = yy - py; - cy = 0; - nd = 1; - } else { - ny = 0; - cy = py - yy; - nd = oldy - cy + 1; - } - while (ny > 0) { - window_copy_cursor_up(wme, 1); - ny--; - } - window_copy_update_cursor(wme, px, cy); - if (window_copy_update_selection(wme, 1, 0)) - window_copy_redraw_lines(wme, data->cy, nd); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void @@ -5000,3 +4867,58 @@ window_copy_jump_to_mark(struct window_mode_entry *wme) window_copy_update_selection(wme, 0, 0); window_copy_redraw_screen(wme); } + +/* Scroll up if the cursor went off the visible screen. */ +static void +window_copy_acquire_cursor_up(struct window_mode_entry *wme, u_int hsize, + u_int oy, u_int oldy, u_int px, u_int py) +{ + u_int cy, yy, ny, nd; + + yy = hsize - oy; + if (py < yy) { + ny = yy - py; + cy = 0; + nd = 1; + } else { + ny = 0; + cy = py - yy; + nd = oldy - cy + 1; + } + while (ny > 0) { + window_copy_cursor_up(wme, 1); + ny--; + } + window_copy_update_cursor(wme, px, cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, cy, nd); +} + +/* Scroll down if the cursor went off the visible screen. */ +static void +window_copy_acquire_cursor_down(struct window_mode_entry *wme, u_int hsize, + u_int sy, u_int oy, u_int oldy, u_int px, u_int py, int no_reset) +{ + u_int cy, yy, ny, nd; + + cy = py - hsize + oy; + yy = sy - 1; + if (cy > yy) { + ny = cy - yy; + oldy = yy; + nd = 1; + } else { + ny = 0; + nd = cy - oldy + 1; + } + while (ny > 0) { + window_copy_cursor_down(wme, 1); + ny--; + } + if (cy > yy) + window_copy_update_cursor(wme, px, yy); + else + window_copy_update_cursor(wme, px, cy); + if (window_copy_update_selection(wme, 1, no_reset)) + window_copy_redraw_lines(wme, oldy, nd); +} From e858270006a9041b9016ed9e6cc12d622ac8fe31 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Feb 2021 07:09:06 +0000 Subject: [PATCH 76/90] There are many format variables now so allocating all the default ones each time a tree is created is too expensive. Instead, convert them all into callbacks and put them in a static table so they only allocate on demand. The tree remains for the moment for extra (non-default) variables added by for example copy mode or popups. Also reduce expensive calls to localtime_r/strftime. GitHub issue 2253. --- format.c | 2509 ++++++++++++++++++++++++++++++++++++++++--------- tmux.h | 3 +- window-copy.c | 6 +- window.c | 11 - 4 files changed, 2050 insertions(+), 479 deletions(-) diff --git a/format.c b/format.c index 47e0bb29..014f1385 100644 --- a/format.c +++ b/format.c @@ -119,13 +119,23 @@ struct format_entry { RB_ENTRY(format_entry) entry; }; -/* Format entry tree. */ +/* Format type. */ +enum format_type { + FORMAT_TYPE_UNKNOWN, + FORMAT_TYPE_SESSION, + FORMAT_TYPE_WINDOW, + FORMAT_TYPE_PANE +}; + struct format_tree { + enum format_type type; + struct client *c; struct session *s; struct winlink *wl; struct window *w; struct window_pane *wp; + struct paste_buffer *pb; struct cmdq_item *item; struct client *client; @@ -144,6 +154,7 @@ struct format_expand_state { struct format_tree *ft; u_int loop; time_t time; + struct tm tm; int flags; }; @@ -263,6 +274,7 @@ format_copy_state(struct format_expand_state *to, to->ft = from->ft; to->loop = from->loop; to->time = from->time; + memcpy(&to->tm, &from->tm, sizeof to->tm); to->flags = from->flags|flags; } @@ -451,8 +463,21 @@ format_job_timer(__unused int fd, __unused short events, __unused void *arg) evtimer_add(&format_job_event, &tv); } +/* Wrapper for asprintf. */ +static char * printflike(1, 2) +format_printf(const char *fmt, ...) +{ + va_list ap; + char *s; + + va_start(ap, fmt); + xvasprintf(&s, fmt, ap); + va_end(ap); + return (s); +} + /* Callback for host. */ -static char * +static void * format_cb_host(__unused struct format_tree *ft) { char host[HOST_NAME_MAX + 1]; @@ -463,7 +488,7 @@ format_cb_host(__unused struct format_tree *ft) } /* Callback for host_short. */ -static char * +static void * format_cb_host_short(__unused struct format_tree *ft) { char host[HOST_NAME_MAX + 1], *cp; @@ -476,7 +501,7 @@ format_cb_host_short(__unused struct format_tree *ft) } /* Callback for pid. */ -static char * +static void * format_cb_pid(__unused struct format_tree *ft) { char *value; @@ -486,7 +511,7 @@ format_cb_pid(__unused struct format_tree *ft) } /* Callback for session_attached_list. */ -static char * +static void * format_cb_session_attached_list(struct format_tree *ft) { struct session *s = ft->s; @@ -517,7 +542,7 @@ format_cb_session_attached_list(struct format_tree *ft) } /* Callback for session_alerts. */ -static char * +static void * format_cb_session_alerts(struct format_tree *ft) { struct session *s = ft->s; @@ -547,7 +572,7 @@ format_cb_session_alerts(struct format_tree *ft) } /* Callback for session_stack. */ -static char * +static void * format_cb_session_stack(struct format_tree *ft) { struct session *s = ft->s; @@ -569,14 +594,18 @@ format_cb_session_stack(struct format_tree *ft) } /* Callback for window_stack_index. */ -static char * +static void * format_cb_window_stack_index(struct format_tree *ft) { - struct session *s = ft->wl->session; + struct session *s; struct winlink *wl; u_int idx; char *value = NULL; + if (ft->wl == NULL) + return (NULL); + s = ft->wl->session; + idx = 0; TAILQ_FOREACH(wl, &s->lastw, sentry) { idx++; @@ -590,15 +619,19 @@ format_cb_window_stack_index(struct format_tree *ft) } /* Callback for window_linked_sessions_list. */ -static char * +static void * format_cb_window_linked_sessions_list(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct winlink *wl; struct evbuffer *buffer; int size; char *value = NULL; + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; + buffer = evbuffer_new(); if (buffer == NULL) fatalx("out of memory"); @@ -616,14 +649,18 @@ format_cb_window_linked_sessions_list(struct format_tree *ft) } /* Callback for window_active_sessions. */ -static char * +static void * format_cb_window_active_sessions(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct winlink *wl; u_int n = 0; char *value; + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; + TAILQ_FOREACH(wl, &w->winlinks, wentry) { if (wl->session->curw == wl) n++; @@ -634,15 +671,19 @@ format_cb_window_active_sessions(struct format_tree *ft) } /* Callback for window_active_sessions_list. */ -static char * +static void * format_cb_window_active_sessions_list(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct winlink *wl; struct evbuffer *buffer; int size; char *value = NULL; + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; + buffer = evbuffer_new(); if (buffer == NULL) fatalx("out of memory"); @@ -662,15 +703,19 @@ format_cb_window_active_sessions_list(struct format_tree *ft) } /* Callback for window_active_clients. */ -static char * +static void * format_cb_window_active_clients(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct client *loop; struct session *client_session; u_int n = 0; char *value; + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; + TAILQ_FOREACH(loop, &clients, entry) { client_session = loop->session; if (client_session == NULL) @@ -685,16 +730,20 @@ format_cb_window_active_clients(struct format_tree *ft) } /* Callback for window_active_clients_list. */ -static char * +static void * format_cb_window_active_clients_list(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct client *loop; struct session *client_session; struct evbuffer *buffer; int size; char *value = NULL; + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; + buffer = evbuffer_new(); if (buffer == NULL) fatalx("out of memory"); @@ -718,7 +767,7 @@ format_cb_window_active_clients_list(struct format_tree *ft) } /* Callback for window_layout. */ -static char * +static void * format_cb_window_layout(struct format_tree *ft) { struct window *w = ft->w; @@ -732,7 +781,7 @@ format_cb_window_layout(struct format_tree *ft) } /* Callback for window_visible_layout. */ -static char * +static void * format_cb_window_visible_layout(struct format_tree *ft) { struct window *w = ft->w; @@ -744,7 +793,7 @@ format_cb_window_visible_layout(struct format_tree *ft) } /* Callback for pane_start_command. */ -static char * +static void * format_cb_start_command(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -756,7 +805,7 @@ format_cb_start_command(struct format_tree *ft) } /* Callback for pane_current_command. */ -static char * +static void * format_cb_current_command(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -780,7 +829,7 @@ format_cb_current_command(struct format_tree *ft) } /* Callback for pane_current_path. */ -static char * +static void * format_cb_current_path(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -796,7 +845,7 @@ format_cb_current_path(struct format_tree *ft) } /* Callback for history_bytes. */ -static char * +static void * format_cb_history_bytes(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -822,7 +871,7 @@ format_cb_history_bytes(struct format_tree *ft) } /* Callback for history_all_bytes. */ -static char * +static void * format_cb_history_all_bytes(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -849,7 +898,7 @@ format_cb_history_all_bytes(struct format_tree *ft) } /* Callback for pane_tabs. */ -static char * +static void * format_cb_pane_tabs(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -879,7 +928,7 @@ format_cb_pane_tabs(struct format_tree *ft) } /* Callback for pane_fg. */ -static char * +static void * format_cb_pane_fg(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -890,7 +939,7 @@ format_cb_pane_fg(struct format_tree *ft) } /* Callback for pane_bg. */ -static char * +static void * format_cb_pane_bg(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -901,7 +950,7 @@ format_cb_pane_bg(struct format_tree *ft) } /* Callback for session_group_list. */ -static char * +static void * format_cb_session_group_list(struct format_tree *ft) { struct session *s = ft->s; @@ -934,7 +983,7 @@ format_cb_session_group_list(struct format_tree *ft) } /* Callback for session_group_attached_list. */ -static char * +static void * format_cb_session_group_attached_list(struct format_tree *ft) { struct session *s = ft->s, *client_session, *session_loop; @@ -974,7 +1023,7 @@ format_cb_session_group_attached_list(struct format_tree *ft) } /* Callback for pane_in_mode. */ -static char * +static void * format_cb_pane_in_mode(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -986,13 +1035,13 @@ format_cb_pane_in_mode(struct format_tree *ft) return (NULL); TAILQ_FOREACH(wme, &wp->modes, entry) - n++; + n++; xasprintf(&value, "%u", n); return (value); } /* Callback for pane_at_top. */ -static char * +static void * format_cb_pane_at_top(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -1014,7 +1063,7 @@ format_cb_pane_at_top(struct format_tree *ft) } /* Callback for pane_at_bottom. */ -static char * +static void * format_cb_pane_at_bottom(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -1036,7 +1085,7 @@ format_cb_pane_at_bottom(struct format_tree *ft) } /* Callback for cursor_character. */ -static char * +static void * format_cb_cursor_character(struct format_tree *ft) { struct window_pane *wp = ft->wp; @@ -1052,78 +1101,8 @@ format_cb_cursor_character(struct format_tree *ft) return (value); } -/* Return word at given coordinates. Caller frees. */ -char * -format_grid_word(struct grid *gd, u_int x, u_int y) -{ - const struct grid_line *gl; - struct grid_cell gc; - const char *ws; - struct utf8_data *ud = NULL; - u_int end; - size_t size = 0; - int found = 0; - char *s = NULL; - - ws = options_get_string(global_s_options, "word-separators"); - - for (;;) { - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - break; - if (utf8_cstrhas(ws, &gc.data)) { - found = 1; - break; - } - - if (x == 0) { - if (y == 0) - break; - gl = grid_peek_line(gd, y - 1); - if (~gl->flags & GRID_LINE_WRAPPED) - break; - y--; - x = grid_line_length(gd, y); - if (x == 0) - break; - } - x--; - } - for (;;) { - if (found) { - end = grid_line_length(gd, y); - if (end == 0 || x == end - 1) { - if (y == gd->hsize + gd->sy - 1) - break; - gl = grid_peek_line(gd, y); - if (~gl->flags & GRID_LINE_WRAPPED) - break; - y++; - x = 0; - } else - x++; - } - found = 1; - - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - break; - if (utf8_cstrhas(ws, &gc.data)) - break; - - ud = xreallocarray(ud, size + 2, sizeof *ud); - memcpy(&ud[size++], &gc.data, sizeof *ud); - } - if (size != 0) { - ud[size].size = 0; - s = utf8_tocstr(ud); - free(ud); - } - return (s); -} - /* Callback for mouse_word. */ -static char * +static void * format_cb_mouse_word(struct format_tree *ft) { struct window_pane *wp; @@ -1149,34 +1128,8 @@ format_cb_mouse_word(struct format_tree *ft) return (format_grid_word(gd, x, gd->hsize + y)); } -/* Return line at given coordinates. Caller frees. */ -char * -format_grid_line(struct grid *gd, u_int y) -{ - struct grid_cell gc; - struct utf8_data *ud = NULL; - u_int x; - size_t size = 0; - char *s = NULL; - - for (x = 0; x < grid_line_length(gd, y); x++) { - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - break; - - ud = xreallocarray(ud, size + 2, sizeof *ud); - memcpy(&ud[size++], &gc.data, sizeof *ud); - } - if (size != 0) { - ud[size].size = 0; - s = utf8_tocstr(ud); - free(ud); - } - return (s); -} - /* Callback for mouse_line. */ -static char * +static void * format_cb_mouse_line(struct format_tree *ft) { struct window_pane *wp; @@ -1201,6 +1154,1810 @@ format_cb_mouse_line(struct format_tree *ft) return (format_grid_line(gd, gd->hsize + y)); } +/* Callback for alternate_on. */ +static void * +format_cb_alternate_on(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.saved_grid != NULL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for alternate_saved_x. */ +static void * +format_cb_alternate_saved_x(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.saved_cx)); + return (NULL); +} + +/* Callback for alternate_saved_y. */ +static void * +format_cb_alternate_saved_y(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.saved_cy)); + return (NULL); +} + +/* Callback for buffer_name. */ +static void * +format_cb_buffer_name(struct format_tree *ft) +{ + if (ft->pb != NULL) + return (xstrdup(paste_buffer_name(ft->pb))); + return (NULL); +} + +/* Callback for buffer_sample. */ +static void * +format_cb_buffer_sample(struct format_tree *ft) +{ + if (ft->pb != NULL) + return (paste_make_sample(ft->pb)); + return (NULL); +} + +/* Callback for buffer_size. */ +static void * +format_cb_buffer_size(struct format_tree *ft) +{ + size_t size; + + if (ft->pb != NULL) { + paste_buffer_data(ft->pb, &size); + return (format_printf("%zu", size)); + } + return (NULL); +} + +/* Callback for client_cell_height. */ +static void * +format_cb_client_cell_height(struct format_tree *ft) +{ + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) + return (format_printf("%u", ft->c->tty.ypixel)); + return (NULL); +} + +/* Callback for client_cell_width. */ +static void * +format_cb_client_cell_width(struct format_tree *ft) +{ + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) + return (format_printf("%u", ft->c->tty.xpixel)); + return (NULL); +} + +/* Callback for client_control_mode. */ +static void * +format_cb_client_control_mode(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->flags & CLIENT_CONTROL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_discarded. */ +static void * +format_cb_client_discarded(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%zu", ft->c->discarded)); + return (NULL); +} + +/* Callback for client_flags. */ +static void * +format_cb_client_flags(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(server_client_get_flags(ft->c))); + return (NULL); +} + +/* Callback for client_height. */ +static void * +format_cb_client_height(struct format_tree *ft) +{ + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) + return (format_printf("%u", ft->c->tty.sy)); + return (NULL); +} + +/* Callback for client_key_table. */ +static void * +format_cb_client_key_table(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->keytable->name)); + return (NULL); +} + +/* Callback for client_last_session. */ +static void * +format_cb_client_last_session(struct format_tree *ft) +{ + if (ft->c != NULL && + ft->c->last_session != NULL && + session_alive(ft->c->last_session)) + return (xstrdup(ft->c->last_session->name)); + return (NULL); +} + +/* Callback for client_name. */ +static void * +format_cb_client_name(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->name)); + return (NULL); +} + +/* Callback for client_pid. */ +static void * +format_cb_client_pid(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%ld", (long)ft->c->pid)); + return (NULL); +} + +/* Callback for client_prefix. */ +static void * +format_cb_client_prefix(struct format_tree *ft) +{ + const char *name; + + if (ft->c != NULL) { + name = server_client_get_key_table(ft->c); + if (strcmp(ft->c->keytable->name, name) == 0) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_readonly. */ +static void * +format_cb_client_readonly(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->flags & CLIENT_READONLY) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_session. */ +static void * +format_cb_client_session(struct format_tree *ft) +{ + if (ft->c != NULL && ft->c->session != NULL) + return (xstrdup(ft->c->session->name)); + return (NULL); +} + +/* Callback for client_termfeatures. */ +static void * +format_cb_client_termfeatures(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(tty_get_features(ft->c->term_features))); + return (NULL); +} + +/* Callback for client_termname. */ +static void * +format_cb_client_termname(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->term_name)); + return (NULL); +} + +/* Callback for client_termtype. */ +static void * +format_cb_client_termtype(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->term_type)); + return (NULL); +} + +/* Callback for client_tty. */ +static void * +format_cb_client_tty(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->ttyname)); + return (NULL); +} + +/* Callback for client_utf8. */ +static void * +format_cb_client_utf8(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->flags & CLIENT_UTF8) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_width. */ +static void * +format_cb_client_width(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%u", ft->c->tty.sx)); + return (NULL); +} + +/* Callback for client_written. */ +static void * +format_cb_client_written(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%zu", ft->c->written)); + return (NULL); +} + +/* Callback for cursor_flag. */ +static void * +format_cb_cursor_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_CURSOR) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for cursor_x. */ +static void * +format_cb_cursor_x(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.cx)); + return (NULL); +} + +/* Callback for cursor_y. */ +static void * +format_cb_cursor_y(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.cy)); + return (NULL); +} + +/* Callback for history_limit. */ +static void * +format_cb_history_limit(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.grid->hlimit)); + return (NULL); +} + +/* Callback for history_size. */ +static void * +format_cb_history_size(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.grid->hsize)); + return (NULL); +} + +/* Callback for insert_flag. */ +static void * +format_cb_insert_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_INSERT) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for keypad_cursor_flag. */ +static void * +format_cb_keypad_cursor_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_KCURSOR) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for keypad_flag. */ +static void * +format_cb_keypad_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_KKEYPAD) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_all_flag. */ +static void * +format_cb_mouse_all_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_ALL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_any_flag. */ +static void * +format_cb_mouse_any_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & ALL_MOUSE_MODES) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_button_flag. */ +static void * +format_cb_mouse_button_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_BUTTON) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_pane. */ +static void * +format_cb_mouse_pane(struct format_tree *ft) +{ + struct window_pane *wp; + + if (ft->m.valid) { + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp != NULL) + return (format_printf("%%%u", wp->id)); + return (NULL); + } + return (NULL); +} + +/* Callback for mouse_sgr_flag. */ +static void * +format_cb_mouse_sgr_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_SGR) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_standard_flag. */ +static void * +format_cb_mouse_standard_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_STANDARD) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_utf8_flag. */ +static void * +format_cb_mouse_utf8_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_UTF8) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_x. */ +static void * +format_cb_mouse_x(struct format_tree *ft) +{ + struct window_pane *wp; + u_int x, y; + + if (ft->m.valid) { + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0) + return (format_printf("%u", x)); + return (NULL); + } + return (NULL); +} + +/* Callback for mouse_y. */ +static void * +format_cb_mouse_y(struct format_tree *ft) +{ + struct window_pane *wp; + u_int x, y; + + if (ft->m.valid) { + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0) + return (format_printf("%u", y)); + return (NULL); + } + return (NULL); +} + +/* Callback for origin_flag. */ +static void * +format_cb_origin_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_ORIGIN) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_active. */ +static void * +format_cb_pane_active(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp == ft->wp->window->active) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_at_left. */ +static void * +format_cb_pane_at_left(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->xoff == 0) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_at_right. */ +static void * +format_cb_pane_at_right(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->xoff + ft->wp->sx == ft->wp->window->sx) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_bottom. */ +static void * +format_cb_pane_bottom(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->yoff + ft->wp->sy - 1)); + return (NULL); +} + +/* Callback for pane_dead. */ +static void * +format_cb_pane_dead(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->fd == -1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_dead_status. */ +static void * +format_cb_pane_dead_status(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(wp->status)) + return (format_printf("%d", WEXITSTATUS(wp->status))); + return (NULL); + } + return (NULL); +} + +/* Callback for pane_format. */ +static void * +format_cb_pane_format(struct format_tree *ft) +{ + if (ft->type == FORMAT_TYPE_PANE) + return (xstrdup("1")); + return (xstrdup("0")); +} + +/* Callback for pane_height. */ +static void * +format_cb_pane_height(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->sy)); + return (NULL); +} + +/* Callback for pane_id. */ +static void * +format_cb_pane_id(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%%%u", ft->wp->id)); + return (NULL); +} + +/* Callback for pane_index. */ +static void * +format_cb_pane_index(struct format_tree *ft) +{ + u_int idx; + + if (ft->wp != NULL && window_pane_index(ft->wp, &idx) == 0) + return (format_printf("%u", idx)); + return (NULL); +} + +/* Callback for pane_input_off. */ +static void * +format_cb_pane_input_off(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->flags & PANE_INPUTOFF) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_last. */ +static void * +format_cb_pane_last(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp == ft->wp->window->last) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_left. */ +static void * +format_cb_pane_left(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->xoff)); + return (NULL); +} + +/* Callback for pane_marked. */ +static void * +format_cb_pane_marked(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (server_check_marked() && marked_pane.wp == ft->wp) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_marked_set. */ +static void * +format_cb_pane_marked_set(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (server_check_marked()) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_mode. */ +static void * +format_cb_pane_mode(struct format_tree *ft) +{ + struct window_mode_entry *wme; + + if (ft->wp != NULL) { + wme = TAILQ_FIRST(&ft->wp->modes); + if (wme != NULL) + return (xstrdup(wme->mode->name)); + return (NULL); + } + return (NULL); +} + +/* Callback for pane_path. */ +static void * +format_cb_pane_path(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.path == NULL) + return (xstrdup("")); + return (xstrdup(ft->wp->base.path)); + } + return (NULL); +} + +/* Callback for pane_pid. */ +static void * +format_cb_pane_pid(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%ld", (long)ft->wp->pid)); + return (NULL); +} + +/* Callback for pane_pipe. */ +static void * +format_cb_pane_pipe(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->pipe_fd != -1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_right. */ +static void * +format_cb_pane_right(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->xoff + ft->wp->sx - 1)); + return (NULL); +} + +/* Callback for pane_search_string. */ +static void * +format_cb_pane_search_string(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->searchstr == NULL) + return (xstrdup("")); + return (xstrdup(ft->wp->searchstr)); + } + return (NULL); +} + +/* Callback for pane_synchronized. */ +static void * +format_cb_pane_synchronized(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (options_get_number(ft->wp->options, "synchronize-panes")) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_title. */ +static void * +format_cb_pane_title(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (xstrdup(ft->wp->base.title)); + return (NULL); +} + +/* Callback for pane_top. */ +static void * +format_cb_pane_top(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->yoff)); + return (NULL); +} + +/* Callback for pane_tty. */ +static void * +format_cb_pane_tty(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (xstrdup(ft->wp->tty)); + return (NULL); +} + +/* Callback for pane_width. */ +static void * +format_cb_pane_width(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->sx)); + return (NULL); +} + +/* Callback for scroll_region_lower. */ +static void * +format_cb_scroll_region_lower(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.rlower)); + return (NULL); +} + +/* Callback for scroll_region_upper. */ +static void * +format_cb_scroll_region_upper(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.rupper)); + return (NULL); +} + +/* Callback for session_attached. */ +static void * +format_cb_session_attached(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("%u", ft->s->attached)); + return (NULL); +} + +/* Callback for session_format. */ +static void * +format_cb_session_format(struct format_tree *ft) +{ + if (ft->type == FORMAT_TYPE_SESSION) + return (xstrdup("1")); + return (xstrdup("0")); +} + +/* Callback for session_group. */ +static void * +format_cb_session_group(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) + return (xstrdup(sg->name)); + return (NULL); +} + +/* Callback for session_group_attached. */ +static void * +format_cb_session_group_attached(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) + return (format_printf("%u", session_group_attached_count (sg))); + return (NULL); +} + +/* Callback for session_group_many_attached. */ +static void * +format_cb_session_group_many_attached(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) { + if (session_group_attached_count (sg) > 1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_group_size. */ +static void * +format_cb_session_group_size(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) + return (format_printf("%u", session_group_count (sg))); + return (NULL); +} + +/* Callback for session_grouped. */ +static void * +format_cb_session_grouped(struct format_tree *ft) +{ + if (ft->s != NULL) { + if (session_group_contains(ft->s) != NULL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_id. */ +static void * +format_cb_session_id(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("$%u", ft->s->id)); + return (NULL); +} + +/* Callback for session_many_attached. */ +static void * +format_cb_session_many_attached(struct format_tree *ft) +{ + if (ft->s != NULL) { + if (ft->s->attached > 1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_marked. */ +static void * +format_cb_session_marked(struct format_tree *ft) +{ + if (ft->s != NULL) { + if (server_check_marked() && marked_pane.s == ft->s) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_name. */ +static void * +format_cb_session_name(struct format_tree *ft) +{ + if (ft->s != NULL) + return (xstrdup(ft->s->name)); + return (NULL); +} + +/* Callback for session_path. */ +static void * +format_cb_session_path(struct format_tree *ft) +{ + if (ft->s != NULL) + return (xstrdup(ft->s->cwd)); + return (NULL); +} + +/* Callback for session_windows. */ +static void * +format_cb_session_windows(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf ("%u", winlink_count(&ft->s->windows))); + return (NULL); +} + +/* Callback for socket_path. */ +static void * +format_cb_socket_path(__unused struct format_tree *ft) +{ + return (xstrdup(socket_path)); +} + +/* Callback for version. */ +static void * +format_cb_version(__unused struct format_tree *ft) +{ + return (xstrdup(getversion())); +} + +/* Callback for window_active. */ +static void * +format_cb_window_active(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == ft->wl->session->curw) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_activity_flag. */ +static void * +format_cb_window_activity_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl->flags & WINLINK_ACTIVITY) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_bell_flag. */ +static void * +format_cb_window_bell_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl->flags & WINLINK_BELL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_bigger. */ +static void * +format_cb_window_bigger(struct format_tree *ft) +{ + u_int ox, oy, sx, sy; + + if (ft->c != NULL) { + if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_cell_height. */ +static void * +format_cb_window_cell_height(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->ypixel)); + return (NULL); +} + +/* Callback for window_cell_width. */ +static void * +format_cb_window_cell_width(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->xpixel)); + return (NULL); +} + +/* Callback for window_end_flag. */ +static void * +format_cb_window_end_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == RB_MAX(winlinks, &ft->wl->session->windows)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_flags. */ +static void * +format_cb_window_flags(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (xstrdup(window_printable_flags(ft->wl, 1))); + return (NULL); +} + +/* Callback for window_format. */ +static void * +format_cb_window_format(struct format_tree *ft) +{ + if (ft->type == FORMAT_TYPE_WINDOW) + return (xstrdup("1")); + return (xstrdup("0")); +} + +/* Callback for window_height. */ +static void * +format_cb_window_height(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->sy)); + return (NULL); +} + +/* Callback for window_id. */ +static void * +format_cb_window_id(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("@%u", ft->w->id)); + return (NULL); +} + +/* Callback for window_index. */ +static void * +format_cb_window_index(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (format_printf("%d", ft->wl->idx)); + return (NULL); +} + +/* Callback for window_last_flag. */ +static void * +format_cb_window_last_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == TAILQ_FIRST(&ft->wl->session->lastw)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_linked. */ +static void * +format_cb_window_linked(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (session_is_linked(ft->wl->session, ft->wl->window)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_linked_sessions. */ +static void * +format_cb_window_linked_sessions(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (format_printf("%u", ft->wl->window->references)); + return (NULL); +} + +/* Callback for window_marked_flag. */ +static void * +format_cb_window_marked_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (server_check_marked() && marked_pane.wl == ft->wl) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_name. */ +static void * +format_cb_window_name(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%s", ft->w->name)); + return (NULL); +} + +/* Callback for window_offset_x. */ +static void * +format_cb_window_offset_x(struct format_tree *ft) +{ + u_int ox, oy, sx, sy; + + if (ft->c != NULL) { + if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy)) + return (format_printf("%u", ox)); + return (NULL); + } + return (NULL); +} + +/* Callback for window_offset_y. */ +static void * +format_cb_window_offset_y(struct format_tree *ft) +{ + u_int ox, oy, sx, sy; + + if (ft->c != NULL) { + if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy)) + return (format_printf("%u", oy)); + return (NULL); + } + return (NULL); +} + +/* Callback for window_panes. */ +static void * +format_cb_window_panes(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", window_count_panes(ft->w))); + return (NULL); +} + +/* Callback for window_raw_flags. */ +static void * +format_cb_window_raw_flags(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (xstrdup(window_printable_flags(ft->wl, 0))); + return (NULL); +} + +/* Callback for window_silence_flag. */ +static void * +format_cb_window_silence_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl->flags & WINLINK_SILENCE) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_start_flag. */ +static void * +format_cb_window_start_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == RB_MIN(winlinks, &ft->wl->session->windows)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_width. */ +static void * +format_cb_window_width(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->sx)); + return (NULL); +} + +/* Callback for window_zoomed_flag. */ +static void * +format_cb_window_zoomed_flag(struct format_tree *ft) +{ + if (ft->w != NULL) { + if (ft->w->flags & WINDOW_ZOOMED) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for wrap_flag. */ +static void * +format_cb_wrap_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_WRAP) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for buffer_created. */ +static void * +format_cb_buffer_created(struct format_tree *ft) +{ + static struct timeval tv; + + if (ft->pb != NULL) { + timerclear(&tv); + tv.tv_sec = paste_buffer_created(ft->pb); + return (&tv); + } + return (NULL); +} + +/* Callback for client_activity. */ +static void * +format_cb_client_activity(struct format_tree *ft) +{ + if (ft->c != NULL) + return (&ft->c->activity_time); + return (NULL); +} + +/* Callback for client_created. */ +static void * +format_cb_client_created(struct format_tree *ft) +{ + if (ft->c != NULL) + return (&ft->c->creation_time); + return (NULL); +} + +/* Callback for session_activity. */ +static void * +format_cb_session_activity(struct format_tree *ft) +{ + if (ft->s != NULL) + return (&ft->s->activity_time); + return (NULL); +} + +/* Callback for session_created. */ +static void * +format_cb_session_created(struct format_tree *ft) +{ + if (ft->s != NULL) + return (&ft->s->creation_time); + return (NULL); +} + +/* Callback for session_last_attached. */ +static void * +format_cb_session_last_attached(struct format_tree *ft) +{ + if (ft->s != NULL) + return (&ft->s->last_attached_time); + return (NULL); +} + +/* Callback for start_time. */ +static void * +format_cb_start_time(__unused struct format_tree *ft) +{ + return (&start_time); +} + +/* Callback for window_activity. */ +static void * +format_cb_window_activity(struct format_tree *ft) +{ + if (ft->w != NULL) + return (&ft->w->activity_time); + return (NULL); +} + +/* Callback for buffer_mode_format, */ +static void * +format_cb_buffer_mode_format(__unused struct format_tree *ft) +{ + return (xstrdup(window_buffer_mode.default_format)); +} + +/* Callback for client_mode_format, */ +static void * +format_cb_client_mode_format(__unused struct format_tree *ft) +{ + return (xstrdup(window_client_mode.default_format)); +} + +/* Callback for tree_mode_format, */ +static void * +format_cb_tree_mode_format(__unused struct format_tree *ft) +{ + return (xstrdup(window_tree_mode.default_format)); +} + +/* Format table type. */ +enum format_table_type { + FORMAT_TABLE_STRING, + FORMAT_TABLE_TIME +}; + +/* Format table entry. */ +struct format_table_entry { + const char *key; + enum format_table_type type; + format_cb cb; +}; + +/* + * Format table. Default format variables (that are almost always in the tree + * and where the value is expanded by a callback in this file) are listed here. + * Only variables which are added by the caller go into the tree. + */ +static const struct format_table_entry format_table[] = { + { "alternate_on", FORMAT_TABLE_STRING, + format_cb_alternate_on + }, + { "alternate_saved_x", FORMAT_TABLE_STRING, + format_cb_alternate_saved_x + }, + { "alternate_saved_y", FORMAT_TABLE_STRING, + format_cb_alternate_saved_y + }, + { "buffer_created", FORMAT_TABLE_TIME, + format_cb_buffer_created + }, + { "buffer_mode_format", FORMAT_TABLE_STRING, + format_cb_buffer_mode_format + }, + { "buffer_name", FORMAT_TABLE_STRING, + format_cb_buffer_name + }, + { "buffer_sample", FORMAT_TABLE_STRING, + format_cb_buffer_sample + }, + { "buffer_size", FORMAT_TABLE_STRING, + format_cb_buffer_size + }, + { "client_activity", FORMAT_TABLE_TIME, + format_cb_client_activity + }, + { "client_cell_height", FORMAT_TABLE_STRING, + format_cb_client_cell_height + }, + { "client_cell_width", FORMAT_TABLE_STRING, + format_cb_client_cell_width + }, + { "client_control_mode", FORMAT_TABLE_STRING, + format_cb_client_control_mode + }, + { "client_created", FORMAT_TABLE_TIME, + format_cb_client_created + }, + { "client_discarded", FORMAT_TABLE_STRING, + format_cb_client_discarded + }, + { "client_flags", FORMAT_TABLE_STRING, + format_cb_client_flags + }, + { "client_height", FORMAT_TABLE_STRING, + format_cb_client_height + }, + { "client_key_table", FORMAT_TABLE_STRING, + format_cb_client_key_table + }, + { "client_last_session", FORMAT_TABLE_STRING, + format_cb_client_last_session + }, + { "client_mode_format", FORMAT_TABLE_STRING, + format_cb_client_mode_format + }, + { "client_name", FORMAT_TABLE_STRING, + format_cb_client_name + }, + { "client_pid", FORMAT_TABLE_STRING, + format_cb_client_pid + }, + { "client_prefix", FORMAT_TABLE_STRING, + format_cb_client_prefix + }, + { "client_readonly", FORMAT_TABLE_STRING, + format_cb_client_readonly + }, + { "client_session", FORMAT_TABLE_STRING, + format_cb_client_session + }, + { "client_termfeatures", FORMAT_TABLE_STRING, + format_cb_client_termfeatures + }, + { "client_termname", FORMAT_TABLE_STRING, + format_cb_client_termname + }, + { "client_termtype", FORMAT_TABLE_STRING, + format_cb_client_termtype + }, + { "client_tty", FORMAT_TABLE_STRING, + format_cb_client_tty + }, + { "client_utf8", FORMAT_TABLE_STRING, + format_cb_client_utf8 + }, + { "client_width", FORMAT_TABLE_STRING, + format_cb_client_width + }, + { "client_written", FORMAT_TABLE_STRING, + format_cb_client_written + }, + { "cursor_character", FORMAT_TABLE_STRING, + format_cb_cursor_character + }, + { "cursor_flag", FORMAT_TABLE_STRING, + format_cb_cursor_flag + }, + { "cursor_x", FORMAT_TABLE_STRING, + format_cb_cursor_x + }, + { "cursor_y", FORMAT_TABLE_STRING, + format_cb_cursor_y + }, + { "history_all_bytes", FORMAT_TABLE_STRING, + format_cb_history_all_bytes + }, + { "history_bytes", FORMAT_TABLE_STRING, + format_cb_history_bytes + }, + { "history_limit", FORMAT_TABLE_STRING, + format_cb_history_limit + }, + { "history_size", FORMAT_TABLE_STRING, + format_cb_history_size + }, + { "host", FORMAT_TABLE_STRING, + format_cb_host + }, + { "host_short", FORMAT_TABLE_STRING, + format_cb_host_short + }, + { "insert_flag", FORMAT_TABLE_STRING, + format_cb_insert_flag + }, + { "keypad_cursor_flag", FORMAT_TABLE_STRING, + format_cb_keypad_cursor_flag + }, + { "keypad_flag", FORMAT_TABLE_STRING, + format_cb_keypad_flag + }, + { "mouse_all_flag", FORMAT_TABLE_STRING, + format_cb_mouse_all_flag + }, + { "mouse_any_flag", FORMAT_TABLE_STRING, + format_cb_mouse_any_flag + }, + { "mouse_button_flag", FORMAT_TABLE_STRING, + format_cb_mouse_button_flag + }, + { "mouse_line", FORMAT_TABLE_STRING, + format_cb_mouse_line + }, + { "mouse_pane", FORMAT_TABLE_STRING, + format_cb_mouse_pane + }, + { "mouse_sgr_flag", FORMAT_TABLE_STRING, + format_cb_mouse_sgr_flag + }, + { "mouse_standard_flag", FORMAT_TABLE_STRING, + format_cb_mouse_standard_flag + }, + { "mouse_utf8_flag", FORMAT_TABLE_STRING, + format_cb_mouse_utf8_flag + }, + { "mouse_word", FORMAT_TABLE_STRING, + format_cb_mouse_word + }, + { "mouse_x", FORMAT_TABLE_STRING, + format_cb_mouse_x + }, + { "mouse_y", FORMAT_TABLE_STRING, + format_cb_mouse_y + }, + { "origin_flag", FORMAT_TABLE_STRING, + format_cb_origin_flag + }, + { "pane_active", FORMAT_TABLE_STRING, + format_cb_pane_active + }, + { "pane_at_bottom", FORMAT_TABLE_STRING, + format_cb_pane_at_bottom + }, + { "pane_at_left", FORMAT_TABLE_STRING, + format_cb_pane_at_left + }, + { "pane_at_right", FORMAT_TABLE_STRING, + format_cb_pane_at_right + }, + { "pane_at_top", FORMAT_TABLE_STRING, + format_cb_pane_at_top + }, + { "pane_bg", FORMAT_TABLE_STRING, + format_cb_pane_bg + }, + { "pane_bottom", FORMAT_TABLE_STRING, + format_cb_pane_bottom + }, + { "pane_current_command", FORMAT_TABLE_STRING, + format_cb_current_command + }, + { "pane_current_path", FORMAT_TABLE_STRING, + format_cb_current_path + }, + { "pane_dead", FORMAT_TABLE_STRING, + format_cb_pane_dead + }, + { "pane_dead_status", FORMAT_TABLE_STRING, + format_cb_pane_dead_status + }, + { "pane_fg", FORMAT_TABLE_STRING, + format_cb_pane_fg + }, + { "pane_format", FORMAT_TABLE_STRING, + format_cb_pane_format + }, + { "pane_height", FORMAT_TABLE_STRING, + format_cb_pane_height + }, + { "pane_id", FORMAT_TABLE_STRING, + format_cb_pane_id + }, + { "pane_in_mode", FORMAT_TABLE_STRING, + format_cb_pane_in_mode + }, + { "pane_index", FORMAT_TABLE_STRING, + format_cb_pane_index + }, + { "pane_input_off", FORMAT_TABLE_STRING, + format_cb_pane_input_off + }, + { "pane_last", FORMAT_TABLE_STRING, + format_cb_pane_last + }, + { "pane_left", FORMAT_TABLE_STRING, + format_cb_pane_left + }, + { "pane_marked", FORMAT_TABLE_STRING, + format_cb_pane_marked + }, + { "pane_marked_set", FORMAT_TABLE_STRING, + format_cb_pane_marked_set + }, + { "pane_mode", FORMAT_TABLE_STRING, + format_cb_pane_mode + }, + { "pane_path", FORMAT_TABLE_STRING, + format_cb_pane_path + }, + { "pane_pid", FORMAT_TABLE_STRING, + format_cb_pane_pid + }, + { "pane_pipe", FORMAT_TABLE_STRING, + format_cb_pane_pipe + }, + { "pane_right", FORMAT_TABLE_STRING, + format_cb_pane_right + }, + { "pane_search_string", FORMAT_TABLE_STRING, + format_cb_pane_search_string + }, + { "pane_start_command", FORMAT_TABLE_STRING, + format_cb_start_command + }, + { "pane_synchronized", FORMAT_TABLE_STRING, + format_cb_pane_synchronized + }, + { "pane_tabs", FORMAT_TABLE_STRING, + format_cb_pane_tabs + }, + { "pane_title", FORMAT_TABLE_STRING, + format_cb_pane_title + }, + { "pane_top", FORMAT_TABLE_STRING, + format_cb_pane_top + }, + { "pane_tty", FORMAT_TABLE_STRING, + format_cb_pane_tty + }, + { "pane_width", FORMAT_TABLE_STRING, + format_cb_pane_width + }, + { "pid", FORMAT_TABLE_STRING, + format_cb_pid + }, + { "scroll_region_lower", FORMAT_TABLE_STRING, + format_cb_scroll_region_lower + }, + { "scroll_region_upper", FORMAT_TABLE_STRING, + format_cb_scroll_region_upper + }, + { "session_activity", FORMAT_TABLE_TIME, + format_cb_session_activity + }, + { "session_alerts", FORMAT_TABLE_STRING, + format_cb_session_alerts + }, + { "session_attached", FORMAT_TABLE_STRING, + format_cb_session_attached + }, + { "session_attached_list", FORMAT_TABLE_STRING, + format_cb_session_attached_list + }, + { "session_created", FORMAT_TABLE_TIME, + format_cb_session_created + }, + { "session_format", FORMAT_TABLE_STRING, + format_cb_session_format + }, + { "session_group", FORMAT_TABLE_STRING, + format_cb_session_group + }, + { "session_group_attached", FORMAT_TABLE_STRING, + format_cb_session_group_attached + }, + { "session_group_attached_list", FORMAT_TABLE_STRING, + format_cb_session_group_attached_list + }, + { "session_group_list", FORMAT_TABLE_STRING, + format_cb_session_group_list + }, + { "session_group_many_attached", FORMAT_TABLE_STRING, + format_cb_session_group_many_attached + }, + { "session_group_size", FORMAT_TABLE_STRING, + format_cb_session_group_size + }, + { "session_grouped", FORMAT_TABLE_STRING, + format_cb_session_grouped + }, + { "session_id", FORMAT_TABLE_STRING, + format_cb_session_id + }, + { "session_last_attached", FORMAT_TABLE_TIME, + format_cb_session_last_attached + }, + { "session_many_attached", FORMAT_TABLE_STRING, + format_cb_session_many_attached + }, + { "session_marked", FORMAT_TABLE_STRING, + format_cb_session_marked, + }, + { "session_name", FORMAT_TABLE_STRING, + format_cb_session_name + }, + { "session_path", FORMAT_TABLE_STRING, + format_cb_session_path + }, + { "session_stack", FORMAT_TABLE_STRING, + format_cb_session_stack + }, + { "session_windows", FORMAT_TABLE_STRING, + format_cb_session_windows + }, + { "socket_path", FORMAT_TABLE_STRING, + format_cb_socket_path + }, + { "start_time", FORMAT_TABLE_TIME, + format_cb_start_time + }, + { "tree_mode_format", FORMAT_TABLE_STRING, + format_cb_tree_mode_format + }, + { "version", FORMAT_TABLE_STRING, + format_cb_version + }, + { "window_active", FORMAT_TABLE_STRING, + format_cb_window_active + }, + { "window_active_clients", FORMAT_TABLE_STRING, + format_cb_window_active_clients + }, + { "window_active_clients_list", FORMAT_TABLE_STRING, + format_cb_window_active_clients_list + }, + { "window_active_sessions", FORMAT_TABLE_STRING, + format_cb_window_active_sessions + }, + { "window_active_sessions_list", FORMAT_TABLE_STRING, + format_cb_window_active_sessions_list + }, + { "window_activity", FORMAT_TABLE_TIME, + format_cb_window_activity + }, + { "window_activity_flag", FORMAT_TABLE_STRING, + format_cb_window_activity_flag + }, + { "window_bell_flag", FORMAT_TABLE_STRING, + format_cb_window_bell_flag + }, + { "window_bigger", FORMAT_TABLE_STRING, + format_cb_window_bigger + }, + { "window_cell_height", FORMAT_TABLE_STRING, + format_cb_window_cell_height + }, + { "window_cell_width", FORMAT_TABLE_STRING, + format_cb_window_cell_width + }, + { "window_end_flag", FORMAT_TABLE_STRING, + format_cb_window_end_flag + }, + { "window_flags", FORMAT_TABLE_STRING, + format_cb_window_flags + }, + { "window_format", FORMAT_TABLE_STRING, + format_cb_window_format + }, + { "window_height", FORMAT_TABLE_STRING, + format_cb_window_height + }, + { "window_id", FORMAT_TABLE_STRING, + format_cb_window_id + }, + { "window_index", FORMAT_TABLE_STRING, + format_cb_window_index + }, + { "window_last_flag", FORMAT_TABLE_STRING, + format_cb_window_last_flag + }, + { "window_layout", FORMAT_TABLE_STRING, + format_cb_window_layout + }, + { "window_linked", FORMAT_TABLE_STRING, + format_cb_window_linked + }, + { "window_linked_sessions", FORMAT_TABLE_STRING, + format_cb_window_linked_sessions + }, + { "window_linked_sessions_list", FORMAT_TABLE_STRING, + format_cb_window_linked_sessions_list + }, + { "window_marked_flag", FORMAT_TABLE_STRING, + format_cb_window_marked_flag + }, + { "window_name", FORMAT_TABLE_STRING, + format_cb_window_name + }, + { "window_offset_x", FORMAT_TABLE_STRING, + format_cb_window_offset_x + }, + { "window_offset_y", FORMAT_TABLE_STRING, + format_cb_window_offset_y + }, + { "window_panes", FORMAT_TABLE_STRING, + format_cb_window_panes + }, + { "window_raw_flags", FORMAT_TABLE_STRING, + format_cb_window_raw_flags + }, + { "window_silence_flag", FORMAT_TABLE_STRING, + format_cb_window_silence_flag + }, + { "window_stack_index", FORMAT_TABLE_STRING, + format_cb_window_stack_index + }, + { "window_start_flag", FORMAT_TABLE_STRING, + format_cb_window_start_flag + }, + { "window_visible_layout", FORMAT_TABLE_STRING, + format_cb_window_visible_layout + }, + { "window_width", FORMAT_TABLE_STRING, + format_cb_window_width + }, + { "window_zoomed_flag", FORMAT_TABLE_STRING, + format_cb_window_zoomed_flag + }, + { "wrap_flag", FORMAT_TABLE_STRING, + format_cb_wrap_flag + } +}; + +/* Compare format table entries. */ +static int +format_table_compare(const void *key0, const void *entry0) +{ + const char *key = key0; + const struct format_table_entry *entry = entry0; + + return (strcmp(key, entry->key)); +} + +/* Get a format callback. */ +static struct format_table_entry * +format_table_get(const char *key) +{ + return (bsearch(key, format_table, nitems(format_table), + sizeof *format_table, format_table_compare)); +} + /* Merge one format tree into another. */ void format_merge(struct format_tree *ft, struct format_tree *from) @@ -1226,20 +2983,8 @@ format_create_add_item(struct format_tree *ft, struct cmdq_item *item) { struct key_event *event = cmdq_get_event(item); struct mouse_event *m = &event->m; - struct window_pane *wp; - u_int x, y; cmdq_merge_formats(item, ft); - - if (m->valid && ((wp = cmd_mouse_pane(m, NULL, NULL)) != NULL)) { - format_add(ft, "mouse_pane", "%%%u", wp->id); - if (cmd_mouse_at(wp, m, &x, &y, 0) == 0) { - format_add(ft, "mouse_x", "%u", x); - format_add(ft, "mouse_y", "%u", y); - format_add_cb(ft, "mouse_word", format_cb_mouse_word); - format_add_cb(ft, "mouse_line", format_cb_mouse_line); - } - } memcpy(&ft->m, m, sizeof ft->m); } @@ -1247,9 +2992,7 @@ format_create_add_item(struct format_tree *ft, struct cmdq_item *item) struct format_tree * format_create(struct client *c, struct cmdq_item *item, int tag, int flags) { - struct format_tree *ft; - const struct window_mode **wm; - char tmp[64]; + struct format_tree *ft; if (!event_initialized(&format_job_event)) { evtimer_set(&format_job_event, format_job_timer, NULL); @@ -1268,21 +3011,6 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags) ft->tag = tag; ft->flags = flags; - 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); - format_add(ft, "socket_path", "%s", socket_path); - format_add_tv(ft, "start_time", &start_time); - - for (wm = all_window_modes; *wm != NULL; wm++) { - if ((*wm)->default_format != NULL) { - xsnprintf(tmp, sizeof tmp, "%s_format", (*wm)->name); - tmp[strcspn(tmp, "-")] = '_'; - format_add(ft, tmp, "%s", (*wm)->default_format); - } - } - if (item != NULL) format_create_add_item(ft, item); @@ -1312,9 +3040,28 @@ void format_each(struct format_tree *ft, void (*cb)(const char *, const char *, void *), void *arg) { - struct format_entry *fe; - char s[64]; + const struct format_table_entry *fte; + struct format_entry *fe; + u_int i; + char s[64]; + void *value; + struct timeval *tv; + for (i = 0; i < nitems(format_table); i++) { + fte = &format_table[i]; + + value = fte->cb(ft); + if (value == NULL) + continue; + if (fte->type == FORMAT_TABLE_TIME) { + tv = value; + xsnprintf(s, sizeof s, "%lld", (long long)tv->tv_sec); + cb(fte->key, s, arg); + } else { + cb(fte->key, value, arg); + free(value); + } + } RB_FOREACH(fe, format_entry_tree, &ft->tree) { if (fe->time != 0) { xsnprintf(s, sizeof s, "%lld", (long long)fe->time); @@ -1484,14 +3231,16 @@ static char * format_find(struct format_tree *ft, const char *key, int modifiers, const char *time_format) { - struct format_entry *fe, fe_find; - struct environ_entry *envent; - struct options_entry *o; - int idx; - char *found = NULL, *saved, s[512]; - const char *errstr; - time_t t = 0; - struct tm tm; + struct format_table_entry *fte; + void *value; + struct format_entry *fe, fe_find; + struct environ_entry *envent; + struct options_entry *o; + int idx; + char *found = NULL, *saved, s[512]; + const char *errstr; + time_t t = 0; + struct tm tm; o = options_parse_get(global_options, key, &idx, 0); if (o == NULL && ft->wp != NULL) @@ -1509,6 +3258,15 @@ format_find(struct format_tree *ft, const char *key, int modifiers, goto found; } + fte = format_table_get(key); + if (fte != NULL) { + value = fte->cb(ft); + if (fte->type == FORMAT_TABLE_TIME) + t = ((struct timeval *)value)->tv_sec; + else + found = value; + goto found; + } fe_find.key = (char *)key; fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find); if (fe != NULL) { @@ -2619,7 +4377,6 @@ format_expand1(struct format_expand_state *es, const char *fmt) const char *ptr, *s; size_t off, len, n, outlen; int ch, brackets; - struct tm *tm; char expanded[8192]; if (fmt == NULL || *fmt == '\0') @@ -2631,11 +4388,12 @@ format_expand1(struct format_expand_state *es, const char *fmt) format_log(es, "expanding format: %s", fmt); - if (es->flags & FORMAT_EXPAND_TIME) { - if (es->time == 0) + if ((es->flags & FORMAT_EXPAND_TIME) && strchr(fmt, '%') != NULL) { + if (es->time == 0) { es->time = time(NULL); - tm = localtime(&es->time); - if (strftime(expanded, sizeof expanded, fmt, tm) == 0) { + localtime_r(&es->time, &es->tm); + } + if (strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) { format_log(es, "format is too long"); return (xstrdup("")); } @@ -2887,9 +4645,14 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, if (c != NULL && s != NULL && c->session != s) log_debug("%s: session does not match", __func__); - format_add(ft, "session_format", "%d", s != NULL); - format_add(ft, "window_format", "%d", wl != NULL); - format_add(ft, "pane_format", "%d", wp != NULL); + if (s != NULL) + ft->type = FORMAT_TYPE_SESSION; + else if (wl != NULL) + ft->type = FORMAT_TYPE_WINDOW; + else if (wp != NULL) + ft->type = FORMAT_TYPE_PANE; + else + ft->type = FORMAT_TYPE_UNKNOWN; if (s == NULL && c != NULL) s = c->session; @@ -2916,106 +4679,16 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, static void format_defaults_session(struct format_tree *ft, struct session *s) { - struct session_group *sg; - ft->s = s; - - format_add(ft, "session_name", "%s", s->name); - format_add(ft, "session_path", "%s", s->cwd); - format_add(ft, "session_windows", "%u", winlink_count(&s->windows)); - format_add(ft, "session_id", "$%u", s->id); - - sg = session_group_contains(s); - format_add(ft, "session_grouped", "%d", sg != NULL); - if (sg != NULL) { - 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); - format_add_tv(ft, "session_last_attached", &s->last_attached_time); - format_add_tv(ft, "session_activity", &s->activity_time); - - 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); - - if (server_check_marked() && marked_pane.s == s) - format_add(ft, "session_marked", "1"); - else - format_add(ft, "session_marked", "0"); } /* Set default format keys for a client. */ static void format_defaults_client(struct format_tree *ft, struct client *c) { - struct session *s; - const char *name; - struct tty *tty = &c->tty; - if (ft->s == NULL) ft->s = c->session; ft->c = c; - - format_add(ft, "client_name", "%s", c->name); - format_add(ft, "client_pid", "%ld", (long) c->pid); - format_add(ft, "client_height", "%u", tty->sy); - format_add(ft, "client_width", "%u", tty->sx); - format_add(ft, "client_cell_width", "%u", tty->xpixel); - format_add(ft, "client_cell_height", "%u", tty->ypixel); - format_add(ft, "client_tty", "%s", c->ttyname); - format_add(ft, "client_control_mode", "%d", - !!(c->flags & CLIENT_CONTROL)); - - format_add(ft, "client_termname", "%s", c->term_name); - format_add(ft, "client_termfeatures", "%s", - tty_get_features(c->term_features)); - if (c->term_type != NULL) - format_add(ft, "client_termtype", "%s", c->term_type); - - format_add_tv(ft, "client_created", &c->creation_time); - format_add_tv(ft, "client_activity", &c->activity_time); - - format_add(ft, "client_written", "%zu", c->written); - format_add(ft, "client_discarded", "%zu", c->discarded); - - name = server_client_get_key_table(c); - if (strcmp(c->keytable->name, name) == 0) - format_add(ft, "client_prefix", "%d", 0); - else - format_add(ft, "client_prefix", "%d", 1); - format_add(ft, "client_key_table", "%s", c->keytable->name); - - if (c->flags & CLIENT_UTF8) - format_add(ft, "client_utf8", "%d", 1); - else - format_add(ft, "client_utf8", "%d", 0); - if (c->flags & CLIENT_READONLY) - format_add(ft, "client_readonly", "%d", 1); - else - format_add(ft, "client_readonly", "%d", 0); - format_add(ft, "client_flags", "%s", server_client_get_flags(c)); - - s = c->session; - if (s != NULL) - format_add(ft, "client_session", "%s", s->name); - s = c->last_session; - if (s != NULL && session_alive(s)) - format_add(ft, "client_last_session", "%s", s->name); } /* Set default format keys for a window. */ @@ -3023,221 +4696,131 @@ void format_defaults_window(struct format_tree *ft, struct window *w) { ft->w = w; - - format_add_tv(ft, "window_activity", &w->activity_time); - format_add(ft, "window_id", "@%u", w->id); - format_add(ft, "window_name", "%s", w->name); - format_add(ft, "window_width", "%u", w->sx); - format_add(ft, "window_height", "%u", w->sy); - format_add(ft, "window_cell_width", "%u", w->xpixel); - format_add(ft, "window_cell_height", "%u", w->ypixel); - format_add_cb(ft, "window_layout", format_cb_window_layout); - format_add_cb(ft, "window_visible_layout", - format_cb_window_visible_layout); - format_add(ft, "window_panes", "%u", window_count_panes(w)); - format_add(ft, "window_zoomed_flag", "%d", - !!(w->flags & WINDOW_ZOOMED)); } /* Set default format keys for a winlink. */ static void format_defaults_winlink(struct format_tree *ft, struct winlink *wl) { - struct client *c = ft->c; - struct session *s = wl->session; - struct window *w = wl->window; - int flag; - u_int ox, oy, sx, sy; - if (ft->w == NULL) - format_defaults_window(ft, w); + format_defaults_window(ft, wl->window); ft->wl = wl; - - if (c != NULL) { - flag = tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); - format_add(ft, "window_bigger", "%d", flag); - if (flag) { - format_add(ft, "window_offset_x", "%u", ox); - format_add(ft, "window_offset_y", "%u", oy); - } - } - - format_add(ft, "window_index", "%d", wl->idx); - format_add_cb(ft, "window_stack_index", format_cb_window_stack_index); - format_add(ft, "window_flags", "%s", window_printable_flags(wl, 1)); - format_add(ft, "window_raw_flags", "%s", window_printable_flags(wl, 0)); - 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))); - format_add(ft, "window_end_flag", "%d", - !!(wl == RB_MAX(winlinks, &s->windows))); - - if (server_check_marked() && marked_pane.wl == wl) - format_add(ft, "window_marked_flag", "1"); - else - format_add(ft, "window_marked_flag", "0"); - - format_add(ft, "window_bell_flag", "%d", - !!(wl->flags & WINLINK_BELL)); - format_add(ft, "window_activity_flag", "%d", - !!(wl->flags & WINLINK_ACTIVITY)); - format_add(ft, "window_silence_flag", "%d", - !!(wl->flags & WINLINK_SILENCE)); - 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. */ void format_defaults_pane(struct format_tree *ft, struct window_pane *wp) { - struct window *w = wp->window; - struct grid *gd = wp->base.grid; - int status = wp->status; - u_int idx; struct window_mode_entry *wme; if (ft->w == NULL) - format_defaults_window(ft, w); + format_defaults_window(ft, wp->window); ft->wp = wp; - format_add(ft, "history_size", "%u", gd->hsize); - format_add(ft, "history_limit", "%u", gd->hlimit); - format_add_cb(ft, "history_bytes", format_cb_history_bytes); - format_add_cb(ft, "history_all_bytes", format_cb_history_all_bytes); - - if (window_pane_index(wp, &idx) != 0) - fatalx("index not found"); - format_add(ft, "pane_index", "%u", idx); - - format_add(ft, "pane_width", "%u", wp->sx); - format_add(ft, "pane_height", "%u", wp->sy); - format_add(ft, "pane_title", "%s", wp->base.title); - if (wp->base.path != NULL) - format_add(ft, "pane_path", "%s", wp->base.path); - format_add(ft, "pane_id", "%%%u", wp->id); - format_add(ft, "pane_active", "%d", wp == w->active); - format_add(ft, "pane_input_off", "%d", !!(wp->flags & PANE_INPUTOFF)); - format_add(ft, "pane_pipe", "%d", wp->pipe_fd != -1); - - if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(status)) - format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status)); - if (~wp->flags & PANE_EMPTY) - format_add(ft, "pane_dead", "%d", wp->fd == -1); - else - format_add(ft, "pane_dead", "0"); - format_add(ft, "pane_last", "%d", wp == w->last); - - if (server_check_marked() && marked_pane.wp == wp) - format_add(ft, "pane_marked", "1"); - else - format_add(ft, "pane_marked", "0"); - format_add(ft, "pane_marked_set", "%d", server_check_marked()); - - format_add(ft, "pane_left", "%u", wp->xoff); - format_add(ft, "pane_top", "%u", wp->yoff); - format_add(ft, "pane_right", "%u", wp->xoff + wp->sx - 1); - format_add(ft, "pane_bottom", "%u", wp->yoff + wp->sy - 1); - format_add(ft, "pane_at_left", "%d", wp->xoff == 0); - format_add_cb(ft, "pane_at_top", format_cb_pane_at_top); - format_add(ft, "pane_at_right", "%d", wp->xoff + wp->sx == w->sx); - format_add_cb(ft, "pane_at_bottom", format_cb_pane_at_bottom); - wme = TAILQ_FIRST(&wp->modes); - if (wme != NULL) { - format_add(ft, "pane_mode", "%s", wme->mode->name); - if (wme->mode->formats != NULL) - wme->mode->formats(wme, ft); - } - format_add_cb(ft, "pane_in_mode", format_cb_pane_in_mode); - - format_add(ft, "pane_synchronized", "%d", - !!options_get_number(wp->options, "synchronize-panes")); - if (wp->searchstr != NULL) - format_add(ft, "pane_search_string", "%s", wp->searchstr); - - format_add(ft, "pane_tty", "%s", wp->tty); - format_add(ft, "pane_pid", "%ld", (long) wp->pid); - format_add_cb(ft, "pane_start_command", format_cb_start_command); - format_add_cb(ft, "pane_current_command", format_cb_current_command); - format_add_cb(ft, "pane_current_path", format_cb_current_path); - - format_add(ft, "cursor_x", "%u", wp->base.cx); - format_add(ft, "cursor_y", "%u", wp->base.cy); - format_add_cb(ft, "cursor_character", format_cb_cursor_character); - - format_add(ft, "scroll_region_upper", "%u", wp->base.rupper); - format_add(ft, "scroll_region_lower", "%u", wp->base.rlower); - - format_add(ft, "alternate_on", "%d", wp->base.saved_grid != NULL); - if (wp->base.saved_cx != UINT_MAX) - format_add(ft, "alternate_saved_x", "%u", wp->base.saved_cx); - if (wp->base.saved_cy != UINT_MAX) - format_add(ft, "alternate_saved_y", "%u", wp->base.saved_cy); - - format_add(ft, "cursor_flag", "%d", - !!(wp->base.mode & MODE_CURSOR)); - format_add(ft, "insert_flag", "%d", - !!(wp->base.mode & MODE_INSERT)); - format_add(ft, "keypad_cursor_flag", "%d", - !!(wp->base.mode & MODE_KCURSOR)); - format_add(ft, "keypad_flag", "%d", - !!(wp->base.mode & MODE_KKEYPAD)); - format_add(ft, "wrap_flag", "%d", - !!(wp->base.mode & MODE_WRAP)); - format_add(ft, "origin_flag", "%d", - !!(wp->base.mode & MODE_ORIGIN)); - - format_add(ft, "mouse_any_flag", "%d", - !!(wp->base.mode & ALL_MOUSE_MODES)); - format_add(ft, "mouse_standard_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_STANDARD)); - format_add(ft, "mouse_button_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_BUTTON)); - format_add(ft, "mouse_all_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_ALL)); - format_add(ft, "mouse_utf8_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_UTF8)); - format_add(ft, "mouse_sgr_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_SGR)); - - format_add_cb(ft, "pane_tabs", format_cb_pane_tabs); - format_add_cb(ft, "pane_fg", format_cb_pane_fg); - format_add_cb(ft, "pane_bg", format_cb_pane_bg); + if (wme != NULL && wme->mode->formats != NULL) + wme->mode->formats(wme, ft); } /* Set default format keys for paste buffer. */ void format_defaults_paste_buffer(struct format_tree *ft, struct paste_buffer *pb) { - struct timeval tv; - size_t size; - char *s; - - timerclear(&tv); - tv.tv_sec = paste_buffer_created(pb); - paste_buffer_data(pb, &size); - - format_add(ft, "buffer_size", "%zu", size); - format_add(ft, "buffer_name", "%s", paste_buffer_name(pb)); - format_add_tv(ft, "buffer_created", &tv); - - s = paste_make_sample(pb); - format_add(ft, "buffer_sample", "%s", s); - free(s); + ft->pb = pb; +} + +/* Return word at given coordinates. Caller frees. */ +char * +format_grid_word(struct grid *gd, u_int x, u_int y) +{ + const struct grid_line *gl; + struct grid_cell gc; + const char *ws; + struct utf8_data *ud = NULL; + u_int end; + size_t size = 0; + int found = 0; + char *s = NULL; + + ws = options_get_string(global_s_options, "word-separators"); + + for (;;) { + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + break; + if (utf8_cstrhas(ws, &gc.data)) { + found = 1; + break; + } + + if (x == 0) { + if (y == 0) + break; + gl = grid_peek_line(gd, y - 1); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + y--; + x = grid_line_length(gd, y); + if (x == 0) + break; + } + x--; + } + for (;;) { + if (found) { + end = grid_line_length(gd, y); + if (end == 0 || x == end - 1) { + if (y == gd->hsize + gd->sy - 1) + break; + gl = grid_peek_line(gd, y); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + y++; + x = 0; + } else + x++; + } + found = 1; + + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + break; + if (utf8_cstrhas(ws, &gc.data)) + break; + + ud = xreallocarray(ud, size + 2, sizeof *ud); + memcpy(&ud[size++], &gc.data, sizeof *ud); + } + if (size != 0) { + ud[size].size = 0; + s = utf8_tocstr(ud); + free(ud); + } + return (s); +} + +/* Return line at given coordinates. Caller frees. */ +char * +format_grid_line(struct grid *gd, u_int y) +{ + struct grid_cell gc; + struct utf8_data *ud = NULL; + u_int x; + size_t size = 0; + char *s = NULL; + + for (x = 0; x < grid_line_length(gd, y); x++) { + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + break; + + ud = xreallocarray(ud, size + 2, sizeof *ud); + memcpy(&ud[size++], &gc.data, sizeof *ud); + } + if (size != 0) { + ud[size].size = 0; + s = utf8_tocstr(ud); + free(ud); + } + return (s); } diff --git a/tmux.h b/tmux.h index 4339f6a7..f3f6a867 100644 --- a/tmux.h +++ b/tmux.h @@ -1944,7 +1944,7 @@ char *paste_make_sample(struct paste_buffer *); #define FORMAT_WINDOW 0x40000000U struct format_tree; struct format_modifier; -typedef char *(*format_cb)(struct format_tree *); +typedef void *(*format_cb)(struct format_tree *); const char *format_skip(const char *, const char *); int format_true(const char *); struct format_tree *format_create(struct client *, struct cmdq_item *, int, @@ -2713,7 +2713,6 @@ void screen_alternate_off(struct screen *, struct grid_cell *, int); /* window.c */ extern struct windows windows; extern struct window_pane_tree all_window_panes; -extern const struct window_mode *all_window_modes[]; int window_cmp(struct window *, struct window *); RB_PROTOTYPE(windows, window, entry, window_cmp); int winlink_cmp(struct winlink *, struct winlink *); diff --git a/window-copy.c b/window-copy.c index af8d2937..2f4b06e8 100644 --- a/window-copy.c +++ b/window-copy.c @@ -710,7 +710,7 @@ window_copy_get_line(struct window_pane *wp, u_int y) return (format_grid_line(gd, gd->hsize + y)); } -static char * +static void * window_copy_cursor_word_cb(struct format_tree *ft) { struct window_pane *wp = format_get_pane(ft); @@ -720,7 +720,7 @@ window_copy_cursor_word_cb(struct format_tree *ft) return (window_copy_get_word(wp, data->cx, data->cy)); } -static char * +static void * window_copy_cursor_line_cb(struct format_tree *ft) { struct window_pane *wp = format_get_pane(ft); @@ -730,7 +730,7 @@ window_copy_cursor_line_cb(struct format_tree *ft) return (window_copy_get_line(wp, data->cy)); } -static char * +static void * window_copy_search_match_cb(struct format_tree *ft) { struct window_pane *wp = format_get_pane(ft); diff --git a/window.c b/window.c index 6bfdb1cd..07ef513f 100644 --- a/window.c +++ b/window.c @@ -63,17 +63,6 @@ static u_int next_window_pane_id; static u_int next_window_id; static u_int next_active_point; -/* List of window modes. */ -const struct window_mode *all_window_modes[] = { - &window_buffer_mode, - &window_client_mode, - &window_clock_mode, - &window_copy_mode, - &window_tree_mode, - &window_view_mode, - NULL -}; - struct window_pane_input_data { struct cmdq_item *item; u_int wp; From 6876381276ff2c2a40d304ada27651fdaf1cd8a7 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Feb 2021 08:18:13 +0000 Subject: [PATCH 77/90] Move config file path expansion much earlier, keep the list of paths around rather than freeing later, and add a config_files format variable containing it. Suggested by kn@ a while back. --- cfg.c | 30 +++++++++++------------------- format.c | 23 +++++++++++++++++++++++ tmux.1 | 1 + tmux.c | 37 +++++++++++++++++++++++++------------ tmux.h | 6 +++--- 5 files changed, 63 insertions(+), 34 deletions(-) diff --git a/cfg.c b/cfg.c index 7c01f614..55c91bc4 100644 --- a/cfg.c +++ b/cfg.c @@ -28,12 +28,15 @@ #include "tmux.h" struct client *cfg_client; -static char *cfg_file; int cfg_finished; static char **cfg_causes; static u_int cfg_ncauses; static struct cmdq_item *cfg_item; +int cfg_quiet = 1; +char **cfg_files; +u_int cfg_nfiles; + static enum cmd_retval cfg_client_done(__unused struct cmdq_item *item, __unused void *data) { @@ -60,19 +63,11 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) return (CMD_RETURN_NORMAL); } -void -set_cfg_file(const char *path) -{ - free(cfg_file); - cfg_file = xstrdup(path); -} - void start_cfg(void) { struct client *c; - char **paths; - u_int i, n; + u_int i; /* * Configuration files are loaded without a client, so commands are run @@ -90,15 +85,12 @@ start_cfg(void) cmdq_append(c, cfg_item); } - if (cfg_file == NULL) { - expand_paths(TMUX_CONF, &paths, &n); - for (i = 0; i < n; i++) { - load_cfg(paths[i], c, NULL, CMD_PARSE_QUIET, NULL); - free(paths[i]); - } - free(paths); - } else - load_cfg(cfg_file, c, NULL, 0, NULL); + for (i = 0; i < cfg_nfiles; i++) { + if (cfg_quiet) + load_cfg(cfg_files[i], c, NULL, CMD_PARSE_QUIET, NULL); + else + load_cfg(cfg_files[i], c, NULL, 0, NULL); + } cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); } diff --git a/format.c b/format.c index 014f1385..9fa6fc47 100644 --- a/format.c +++ b/format.c @@ -1412,6 +1412,26 @@ format_cb_client_written(struct format_tree *ft) return (NULL); } +/* Callback for config_files. */ +static void * +format_cb_config_files(__unused struct format_tree *ft) +{ + char *s = NULL; + size_t slen = 0; + u_int i; + size_t n; + + for (i = 0; i < cfg_nfiles; i++) { + n = strlen(cfg_files[i]) + 1; + s = xrealloc(s, slen + n + 1); + slen += xsnprintf(s + slen, n + 1, "%s,", cfg_files[i]); + } + if (s == NULL) + return (xstrdup("")); + s[slen - 1] = '\0'; + return (s); +} + /* Callback for cursor_flag. */ static void * format_cb_cursor_flag(struct format_tree *ft) @@ -2569,6 +2589,9 @@ static const struct format_table_entry format_table[] = { { "client_written", FORMAT_TABLE_STRING, format_cb_client_written }, + { "config_files", FORMAT_TABLE_STRING, + format_cb_config_files + }, { "cursor_character", FORMAT_TABLE_STRING, format_cb_cursor_character }, diff --git a/tmux.1 b/tmux.1 index 9150c4a6..c3164e8f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4763,6 +4763,7 @@ The following variables are available, where appropriate: .It Li "buffer_name" Ta "" Ta "Name of buffer" .It Li "buffer_sample" Ta "" Ta "Sample of start of buffer" .It Li "buffer_size" Ta "" Ta "Size of the specified buffer in bytes" +.It Li "config_files" Ta "" Ta "List of configuration files loaded" .It Li "client_activity" Ta "" Ta "Time client last had activity" .It Li "client_cell_height" Ta "" Ta "Height of each client cell in pixels" .It Li "client_cell_width" Ta "" Ta "Width of each client cell in pixels" diff --git a/tmux.c b/tmux.c index 5861e66b..fb908031 100644 --- a/tmux.c +++ b/tmux.c @@ -142,11 +142,12 @@ expand_path(const char *path, const char *home) return (xstrdup(path)); } -void -expand_paths(const char *s, char ***paths, u_int *n) +static void +expand_paths(const char *s, char ***paths, u_int *n, int ignore_errors) { const char *home = find_home(); char *copy, *next, *tmp, resolved[PATH_MAX], *expanded; + char *path; u_int i; *paths = NULL; @@ -162,20 +163,26 @@ expand_paths(const char *s, char ***paths, u_int *n) if (realpath(expanded, resolved) == NULL) { log_debug("%s: realpath(\"%s\") failed: %s", __func__, expanded, strerror(errno)); + if (ignore_errors) { + free(expanded); + continue; + } + path = expanded; + } else { + path = xstrdup(resolved); free(expanded); - continue; } - free(expanded); for (i = 0; i < *n; i++) { - if (strcmp(resolved, (*paths)[i]) == 0) + if (strcmp(path, (*paths)[i]) == 0) break; } if (i != *n) { - log_debug("%s: duplicate path: %s", __func__, resolved); + log_debug("%s: duplicate path: %s", __func__, path); + free(path); continue; } *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths); - (*paths)[(*n)++] = xstrdup(resolved); + (*paths)[(*n)++] = path; } free(copy); } @@ -193,7 +200,7 @@ make_label(const char *label, char **cause) label = "default"; uid = getuid(); - expand_paths(TMUX_SOCK, &paths, &n); + expand_paths(TMUX_SOCK, &paths, &n, 1); if (n == 0) { xasprintf(cause, "no suitable socket path"); return (NULL); @@ -330,10 +337,11 @@ main(int argc, char **argv) { char *path = NULL, *label = NULL; char *cause, **var; - const char *s, *shell, *cwd; + const char *s, *cwd; int opt, keys, feat = 0; uint64_t flags = 0; const struct options_table_entry *oe; + u_int i; if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && setlocale(LC_CTYPE, "C.UTF-8") == NULL) { @@ -349,6 +357,7 @@ main(int argc, char **argv) if (**argv == '-') flags = CLIENT_LOGIN; + expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1); while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) { switch (opt) { @@ -368,7 +377,11 @@ main(int argc, char **argv) flags |= CLIENT_CONTROL; break; case 'f': - set_cfg_file(optarg); + for (i = 0; i < cfg_nfiles; i++) + free(cfg_files[i]); + free(cfg_files); + expand_paths(optarg, &cfg_files, &cfg_nfiles, 0); + cfg_quiet = 0; break; case 'V': printf("%s %s\n", getprogname(), getversion()); @@ -460,8 +473,8 @@ main(int argc, char **argv) * The default shell comes from SHELL or from the user's passwd entry * if available. */ - shell = getshell(); - options_set_string(global_s_options, "default-shell", 0, "%s", shell); + options_set_string(global_s_options, "default-shell", 0, "%s", + getshell()); /* Override keys to vi if VISUAL or EDITOR are set. */ if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { diff --git a/tmux.h b/tmux.h index f3f6a867..fb15356a 100644 --- a/tmux.h +++ b/tmux.h @@ -1887,7 +1887,6 @@ const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); const char *getversion(void); -void expand_paths(const char *, char ***, u_int *); /* proc.c */ struct imsg; @@ -1907,13 +1906,14 @@ pid_t proc_fork_and_daemon(int *); /* cfg.c */ extern int cfg_finished; extern struct client *cfg_client; +extern char **cfg_files; +extern u_int cfg_nfiles; +extern int cfg_quiet; void start_cfg(void); int load_cfg(const char *, struct client *, struct cmdq_item *, int, struct cmdq_item **); int load_cfg_from_buffer(const void *, size_t, const char *, struct client *, struct cmdq_item *, int, struct cmdq_item **); -void set_cfg_file(const char *); -const char *get_cfg_file(void); void printflike(1, 2) cfg_add_cause(const char *, ...); void cfg_print_causes(struct cmdq_item *); void cfg_show_causes(struct session *); From 5f425ee31810c964ae5cf1256d0d7fe5dde7536c Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Feb 2021 08:31:19 +0000 Subject: [PATCH 78/90] Fix regex searching with wrapped lines, from Anindya Mukherjee; GitHub issue 2570. --- window-copy.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/window-copy.c b/window-copy.c index 2f4b06e8..531431c8 100644 --- a/window-copy.c +++ b/window-copy.c @@ -73,6 +73,8 @@ static int window_copy_search_marks(struct window_mode_entry *, static void window_copy_clear_marks(struct window_mode_entry *); static void window_copy_move_left(struct screen *, u_int *, u_int *, int); static int window_copy_is_lowercase(const char *); +static void window_copy_search_back_overlap(struct grid *, regex_t *, + u_int *, u_int *, u_int *, u_int); static int window_copy_search_jump(struct window_mode_entry *, struct grid *, struct grid *, u_int, u_int, u_int, int, int, int, int, u_int *); @@ -2912,6 +2914,48 @@ window_copy_is_lowercase(const char *ptr) return (1); } +/* + * Handle backward wrapped regex searches with overlapping matches. In this case + * find the longest overlapping match from previous wrapped lines. + */ +static void +window_copy_search_back_overlap(struct grid *gd, regex_t *preg, u_int *ppx, + u_int *psx, u_int *ppy, u_int endline) +{ + u_int endx, endy, oldendx, oldendy, px, py, sx; + int found = 1; + + oldendx = *ppx + *psx; + oldendy = *ppy - 1; + while (oldendx > gd->sx - 1) { + oldendx -= gd->sx; + oldendy++; + } + endx = oldendx; + endy = oldendy; + px = *ppx; + py = *ppy; + while (found && px == 0 && py - 1 > endline && + grid_get_line(gd, py - 2)->flags & GRID_LINE_WRAPPED && + endx == oldendx && endy == oldendy) { + py--; + found = window_copy_search_rl_regex(gd, &px, &sx, py - 1, 0, + gd->sx, preg); + if (found) { + endx = px + sx; + endy = py - 1; + while (endx > gd->sx - 1) { + endx -= gd->sx; + endy++; + } + if (endx == oldendx && endy == oldendy) { + *ppx = px; + *ppy = py; + } + } + } +} + /* * Search for text stored in sgd starting from position fx,fy up to endline. If * found, jump to it. If cis then ignore case. The direction is 0 for searching @@ -2964,6 +3008,10 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, if (regex) { found = window_copy_search_rl_regex(gd, &px, &sx, i - 1, 0, fx + 1, ®); + if (found) { + window_copy_search_back_overlap(gd, + ®, &px, &sx, &i, endline); + } } else { found = window_copy_search_rl(gd, sgd, &px, i - 1, 0, fx + 1, cis); @@ -3048,6 +3096,12 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex, if (found) { window_copy_search_marks(wme, &ss, regex, visible_only); if (foundlen != 0) { + /* Adjust for wrapped lines eating one right. */ + i = data->cx + foundlen; + while (i > gd->sx - 1) { + i -= gd->sx; + window_copy_cursor_right(wme, 1); + } for (i = 0; i < foundlen; i++) window_copy_cursor_right(wme, 1); } @@ -3164,8 +3218,11 @@ again: if (window_copy_search_mark_at(data, px, py, &b) == 0) { if (b + width > gd->sx * gd->sy) width = (gd->sx * gd->sy) - b; - for (i = b; i < b + width; i++) + for (i = b; i < b + width; i++) { + if (data->searchmark[i] != 0) + continue; data->searchmark[i] = data->searchgen; + } if (data->searchgen == UCHAR_MAX) data->searchgen = 1; else From 6d8efe9319857c0b1bc49118248efe7f55112bb5 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 22 Feb 2021 11:42:50 +0000 Subject: [PATCH 79/90] expand_paths needs the global environment to be set up, do that first. --- tmux.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tmux.c b/tmux.c index fb908031..71cbccba 100644 --- a/tmux.c +++ b/tmux.c @@ -357,6 +357,12 @@ main(int argc, char **argv) if (**argv == '-') flags = CLIENT_LOGIN; + + global_environ = environ_create(); + for (var = environ; *var != NULL; var++) + environ_put(global_environ, *var, 0); + if ((cwd = find_cwd()) != NULL) + environ_set(global_environ, "PWD", 0, "%s", cwd); expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1); while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) { @@ -451,12 +457,6 @@ main(int argc, char **argv) flags |= CLIENT_UTF8; } - global_environ = environ_create(); - for (var = environ; *var != NULL; var++) - environ_put(global_environ, *var, 0); - if ((cwd = find_cwd()) != NULL) - environ_set(global_environ, "PWD", 0, "%s", cwd); - global_options = options_create(NULL); global_s_options = options_create(NULL); global_w_options = options_create(NULL); From 6aaef3e705507566631905560b1c903f8ae22ab3 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 24 Feb 2021 09:22:15 +0000 Subject: [PATCH 80/90] Correct client_prefix so it returns 1 if in prefix, not 0. --- format.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index 9fa6fc47..a9694bab 100644 --- a/format.c +++ b/format.c @@ -1319,8 +1319,8 @@ format_cb_client_prefix(struct format_tree *ft) if (ft->c != NULL) { name = server_client_get_key_table(ft->c); if (strcmp(ft->c->keytable->name, name) == 0) - return (xstrdup("1")); - return (xstrdup("0")); + return (xstrdup("0")); + return (xstrdup("1")); } return (NULL); } From dd7006c850e1f973c214f70eee87a054b00f19e7 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Feb 2021 07:53:26 +0000 Subject: [PATCH 81/90] Add a couple of format variables for active and last window index. --- format.c | 28 ++++++++++++++++++++++++++++ tmux.1 | 6 ++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index a9694bab..00413bae 100644 --- a/format.c +++ b/format.c @@ -2099,6 +2099,28 @@ format_cb_version(__unused struct format_tree *ft) return (xstrdup(getversion())); } +/* Callback for active_window_index. */ +static void * +format_cb_active_window_index(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("%u", ft->s->curw->idx)); + return (NULL); +} + +/* Callback for last_window_index. */ +static void * +format_cb_last_window_index(struct format_tree *ft) +{ + struct winlink *wl; + + if (ft->s != NULL) { + wl = RB_MAX(winlinks, &ft->s->windows); + return (format_printf("%u", wl->idx)); + } + return (NULL); +} + /* Callback for window_active. */ static void * format_cb_window_active(struct format_tree *ft) @@ -2496,6 +2518,9 @@ struct format_table_entry { * Only variables which are added by the caller go into the tree. */ static const struct format_table_entry format_table[] = { + { "active_window_index", FORMAT_TABLE_STRING, + format_cb_active_window_index + }, { "alternate_on", FORMAT_TABLE_STRING, format_cb_alternate_on }, @@ -2631,6 +2656,9 @@ static const struct format_table_entry format_table[] = { { "keypad_flag", FORMAT_TABLE_STRING, format_cb_keypad_flag }, + { "last_window_index", FORMAT_TABLE_STRING, + format_cb_last_window_index + }, { "mouse_all_flag", FORMAT_TABLE_STRING, format_cb_mouse_all_flag }, diff --git a/tmux.1 b/tmux.1 index c3164e8f..a88a1d34 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4756,6 +4756,7 @@ will be replaced by The following variables are available, where appropriate: .Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX" .It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with" +.It Li "active_window_index" Ta "" Ta "Index of active window in session" .It Li "alternate_on" Ta "" Ta "1 if pane is in alternate screen" .It Li "alternate_saved_x" Ta "" Ta "Saved cursor X in alternate screen" .It Li "alternate_saved_y" Ta "" Ta "Saved cursor Y in alternate screen" @@ -4763,7 +4764,6 @@ The following variables are available, where appropriate: .It Li "buffer_name" Ta "" Ta "Name of buffer" .It Li "buffer_sample" Ta "" Ta "Sample of start of buffer" .It Li "buffer_size" Ta "" Ta "Size of the specified buffer in bytes" -.It Li "config_files" Ta "" Ta "List of configuration files loaded" .It Li "client_activity" Ta "" Ta "Time client last had activity" .It Li "client_cell_height" Ta "" Ta "Height of each client cell in pixels" .It Li "client_cell_width" Ta "" Ta "Width of each client cell in pixels" @@ -4790,6 +4790,7 @@ The following variables are available, where appropriate: .It Li "command_list_alias" Ta "" Ta "Command alias if listing commands" .It Li "command_list_name" Ta "" Ta "Command name if listing commands" .It Li "command_list_usage" Ta "" Ta "Command usage if listing commands" +.It Li "config_files" Ta "" Ta "List of configuration files loaded" .It Li "copy_cursor_line" Ta "" Ta "Line the cursor is on in copy mode" .It Li "copy_cursor_word" Ta "" Ta "Word under cursor in copy mode" .It Li "copy_cursor_x" Ta "" Ta "Cursor X position in copy mode" @@ -4812,6 +4813,7 @@ The following variables are available, where appropriate: .It Li "insert_flag" Ta "" Ta "Pane insert flag" .It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag" .It Li "keypad_flag" Ta "" Ta "Pane keypad flag" +.It Li "last_window_index" Ta "" Ta "Index of last window in session" .It Li "line" Ta "" Ta "Line number in the list" .It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" .It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag" @@ -4867,8 +4869,8 @@ The following variables are available, where appropriate: .It Li "scroll_position" Ta "" Ta "Scroll position in copy mode" .It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane" .It Li "scroll_region_upper" Ta "" Ta "Top of scroll region in pane" -.It Li "search_present" Ta "" Ta "1 if search started in copy mode" .It Li "search_match" Ta "" Ta "Search match if any" +.It Li "search_present" Ta "" Ta "1 if search started in copy mode" .It Li "selection_active" Ta "" Ta "1 if selection started and changes with the cursor in copy mode" .It Li "selection_end_x" Ta "" Ta "X position of the end of the selection" .It Li "selection_end_y" Ta "" Ta "Y position of the end of the selection" From 583aaebc0ac437777ce796fc6cc29de7d488cf1a Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 26 Feb 2021 21:53:41 +0000 Subject: [PATCH 82/90] Check session, window, pane in the right order when working out format type. --- format.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/format.c b/format.c index 00413bae..46af036c 100644 --- a/format.c +++ b/format.c @@ -4696,12 +4696,12 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, if (c != NULL && s != NULL && c->session != s) log_debug("%s: session does not match", __func__); - if (s != NULL) - ft->type = FORMAT_TYPE_SESSION; + if (wp != NULL) + ft->type = FORMAT_TYPE_PANE; else if (wl != NULL) ft->type = FORMAT_TYPE_WINDOW; - else if (wp != NULL) - ft->type = FORMAT_TYPE_PANE; + else if (s != NULL) + ft->type = FORMAT_TYPE_SESSION; else ft->type = FORMAT_TYPE_UNKNOWN; From 40ad11b2b5155dbccf15881500f2b4f8b16d509d Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 27 Feb 2021 06:28:16 +0000 Subject: [PATCH 83/90] Handle NULL term_type. --- format.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/format.c b/format.c index 46af036c..c8550907 100644 --- a/format.c +++ b/format.c @@ -1368,8 +1368,11 @@ format_cb_client_termname(struct format_tree *ft) static void * format_cb_client_termtype(struct format_tree *ft) { - if (ft->c != NULL) + if (ft->c != NULL) { + if (ft->c->term_type == NULL) + return (xstrdup("")); return (xstrdup(ft->c->term_type)); + } return (NULL); } From b6dfb9996a9f73c418b2291a8c29d345966eae7d Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Mar 2021 10:44:38 +0000 Subject: [PATCH 84/90] Add some text with examples of ; as a separator, GitHub issues 2522 and 2580. --- tmux.1 | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tmux.1 b/tmux.1 index a88a1d34..ee07546b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -522,6 +522,69 @@ Commands separated by semicolons together form a - if a command in the sequence encounters an error, no subsequent commands are executed. .Pp +It is recommended that a semicolon used as a command separator should be +written as an individual token, for example from +.Xr sh 1 : +.Bd -literal -offset indent +$ tmux neww \\; splitw +.Ed +.Pp +Or: +.Bd -literal -offset indent +$ tmux neww ';' splitw +.Ed +.Pp +Or from the tmux command prompt: +.Bd -literal -offset indent +neww ; splitw +.Ed +.Pp +However, a trailing semicolon is also interpreted as a command separator, +for example in these +.Xr sh 1 +commands: +.Bd -literal -offset indent +$ tmux neww\\; splitw +.Ed +.Pp +Or: +.Bd -literal -offset indent +$ tmux 'neww;' splitw +.Ed +.Pp +As in these examples, when running tmux from the shell extra care must be taken +to properly quote semicolons: +.Bl -enum -offset Ds +.It +Semicolons that should be interpreted as a command separator +should be escaped according to the shell conventions. +For +.Xr sh 1 +this typically means quoted (such as +.Ql neww ';' splitw ) +or escaped (such as +.Ql neww \\\\; splitw ) . +.It +Individual semicolons or trailing semicolons that should be interpreted as +arguments should be escaped twice: once according to the shell conventions and +a second time for +.Nm ; +for example: +.Bd -literal -offset indent +$ tmux neww 'foo\\;' bar +$ tmux neww foo\\\\; bar +.Ed +.Pp +.It +Semicolons that are not individual tokens or trailing another token should only +be escaped once according to shell conventions; for example: +.Bd -literal -offset indent +$ tmux neww 'foo-;-bar' +$ tmux neww foo-\\;-bar +.Ed +.Pp +.El +.Pp Comments are marked by the unquoted # character - any remaining text after a comment is ignored until the end of the line. .Pp From 8a4a2153fdec71397fe555073d4d77ee68947fff Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Mar 2021 10:50:14 +0000 Subject: [PATCH 85/90] There is no need to call del_curterm in the server anymore. --- tty-term.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/tty-term.c b/tty-term.c index c8048f77..9df50948 100644 --- a/tty-term.c +++ b/tty-term.c @@ -521,9 +521,6 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps, a = options_array_next(a); } - /* Delete curses data. */ - del_curterm(cur_term); - /* Apply overrides so any capabilities used for features are changed. */ tty_term_apply_overrides(term); From 9cd45ddad3141370e0dac117388984a3f026c097 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 1 Mar 2021 10:51:24 +0000 Subject: [PATCH 86/90] Reinstate del_curterm ifdef bits. --- tty-term.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tty-term.c b/tty-term.c index cc0b8ceb..1d9b36da 100644 --- a/tty-term.c +++ b/tty-term.c @@ -695,7 +695,10 @@ tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps, (*ncaps)++; } +#if !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR > 5 || \ + (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 6) del_curterm(cur_term); +#endif return (0); } From de3a898e8af335325039a4165525fff1d5e1a1b0 Mon Sep 17 00:00:00 2001 From: jmc Date: Mon, 1 Mar 2021 17:49:08 +0000 Subject: [PATCH 87/90] escape quotes and remove some unneccessary Pp; ok nicm --- tmux.1 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tmux.1 b/tmux.1 index ee07546b..4046dbf7 100644 --- a/tmux.1 +++ b/tmux.1 @@ -544,7 +544,7 @@ for example in these .Xr sh 1 commands: .Bd -literal -offset indent -$ tmux neww\\; splitw +$ tmux neww\e\e; splitw .Ed .Pp Or: @@ -563,7 +563,7 @@ For this typically means quoted (such as .Ql neww ';' splitw ) or escaped (such as -.Ql neww \\\\; splitw ) . +.Ql neww \e\e\e\e; splitw ) . .It Individual semicolons or trailing semicolons that should be interpreted as arguments should be escaped twice: once according to the shell conventions and @@ -571,18 +571,16 @@ a second time for .Nm ; for example: .Bd -literal -offset indent -$ tmux neww 'foo\\;' bar -$ tmux neww foo\\\\; bar +$ tmux neww 'foo\e\e;' bar +$ tmux neww foo\e\e\e\e; bar .Ed -.Pp .It Semicolons that are not individual tokens or trailing another token should only be escaped once according to shell conventions; for example: .Bd -literal -offset indent $ tmux neww 'foo-;-bar' -$ tmux neww foo-\\;-bar +$ tmux neww foo-\e\e;-bar .Ed -.Pp .El .Pp Comments are marked by the unquoted # character - any remaining text after a From c44750792a9683c5cd6f9df5a69e7417b88772d2 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 2 Mar 2021 10:56:45 +0000 Subject: [PATCH 88/90] Drop support for popups where the content is provided directly to tmux (which does not have many practical uses) and only support running a program in the popup. display-popup is now simpler and can accept multiple arguments to avoid escaping problems (like the other commands). --- cmd-display-menu.c | 59 ++++++------- cmd-if-shell.c | 2 +- cmd-run-shell.c | 2 +- format.c | 2 +- job.c | 46 ++++++---- popup.c | 207 ++++----------------------------------------- spawn.c | 5 +- tmux.1 | 61 ++----------- tmux.h | 19 ++--- window-copy.c | 4 +- 10 files changed, 97 insertions(+), 310 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index ae000abe..27a4c1d7 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -18,6 +18,7 @@ #include +#include #include #include @@ -50,10 +51,10 @@ const struct cmd_entry cmd_display_popup_entry = { .name = "display-popup", .alias = "popup", - .args = { "CEKc:d:h:R:t:w:x:y:", 0, -1 }, - .usage = "[-CEK] [-c target-client] [-d start-directory] [-h height] " - "[-R shell-command] " CMD_TARGET_PANE_USAGE " [-w width] " - "[-x position] [-y position] [command line ...]", + .args = { "Cc:d:Eh:t:w:x:y:", 0, -1 }, + .usage = "[-CE] [-c target-client] [-d start-directory] [-h height] " + CMD_TARGET_PANE_USAGE " [-w width] " + "[-x position] [-y position] [command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -325,13 +326,14 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); + struct session *s = target->s; struct client *tc = cmdq_get_target_client(item); struct tty *tty = &tc->tty; - const char *value, *cmd = NULL, **lines = NULL; + const char *value, *shell[] = { NULL, NULL }; const char *shellcmd = NULL; - char *cwd, *cause; - int flags = 0; - u_int px, py, w, h, nlines = 0; + char *cwd, *cause, **argv = args->argv; + int flags = 0, argc = args->argc; + u_int px, py, w, h; if (args_has(args, 'C')) { server_client_clear_overlay(tc); @@ -340,17 +342,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) if (tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); - if (args->argc >= 1) - cmd = args->argv[0]; - if (args->argc >= 2) { - lines = (const char **)args->argv + 1; - nlines = args->argc - 1; - } - - if (nlines != 0) - h = popup_height(nlines, lines) + 2; - else - h = tty->sy / 2; + h = tty->sy / 2; if (args_has(args, 'h')) { h = args_percentage(args, 'h', 1, tty->sy, tty->sy, &cause); if (cause != NULL) { @@ -360,10 +352,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) } } - if (nlines != 0) - w = popup_width(item, nlines, lines, tc, target) + 2; - else - w = tty->sx / 2; + w = tty->sx / 2; if (args_has(args, 'w')) { w = args_percentage(args, 'w', 1, tty->sx, tty->sx, &cause); if (cause != NULL) { @@ -384,20 +373,26 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) if (value != NULL) cwd = format_single_from_target(item, value); else - cwd = xstrdup(server_client_get_cwd(tc, target->s)); + cwd = xstrdup(server_client_get_cwd(tc, s)); + if (argc == 0) + shellcmd = options_get_string(s->options, "default-command"); + else if (argc == 1) + shellcmd = argv[0]; + if (argc <= 1 && (shellcmd == NULL || *shellcmd == '\0')) { + shellcmd = NULL; + shell[0] = options_get_string(s->options, "default-shell"); + if (!checkshell(shell[0])) + shell[0] = _PATH_BSHELL; + argc = 1; + argv = (char**)shell; + } - value = args_get(args, 'R'); - if (value != NULL) - shellcmd = format_single_from_target(item, value); - - if (args_has(args, 'K')) - flags |= POPUP_WRITEKEYS; if (args_has(args, 'E') > 1) flags |= POPUP_CLOSEEXITZERO; else if (args_has(args, 'E')) flags |= POPUP_CLOSEEXIT; - if (popup_display(flags, item, px, py, w, h, nlines, lines, shellcmd, - cmd, cwd, tc, target, NULL, NULL) != 0) + if (popup_display(flags, item, px, py, w, h, shellcmd, argc, argv, cwd, + tc, s, NULL, NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } diff --git a/cmd-if-shell.c b/cmd-if-shell.c index d980472a..65fbf19b 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -128,7 +128,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) cdata->input.c->references++; cmd_find_copy_state(&cdata->input.fs, target); - if (job_run(shellcmd, s, + if (job_run(shellcmd, 0, NULL, s, server_client_get_cwd(cmdq_get_client(item), s), NULL, cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1, -1) == NULL) { diff --git a/cmd-run-shell.c b/cmd-run-shell.c index b259276d..4f30d05d 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -174,7 +174,7 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg) enum cmd_parse_status status; if (cmd != NULL && cdata->shell) { - if (job_run(cmd, cdata->s, cdata->cwd, NULL, + if (job_run(cmd, 0, NULL, cdata->s, cdata->cwd, NULL, cmd_run_shell_callback, cmd_run_shell_free, cdata, cdata->flags, -1, -1) == NULL) cmd_run_shell_free(cdata); diff --git a/format.c b/format.c index c8550907..b913f56a 100644 --- a/format.c +++ b/format.c @@ -394,7 +394,7 @@ format_job_get(struct format_expand_state *es, const char *cmd) if (force && fj->job != NULL) job_free(fj->job); if (force || (fj->job == NULL && fj->last != t)) { - fj->job = job_run(expanded, NULL, + fj->job = job_run(expanded, 0, NULL, NULL, server_client_get_cwd(ft->client, NULL), format_job_update, format_job_complete, NULL, fj, JOB_NOWAIT, -1, -1); if (fj->job == NULL) { diff --git a/job.c b/job.c index 6267336b..a972bc0e 100644 --- a/job.c +++ b/job.c @@ -68,19 +68,20 @@ struct job { /* All jobs list. */ static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); -/* Start a job running, if it isn't already. */ +/* Start a job running. */ struct job * -job_run(const char *cmd, struct session *s, const char *cwd, - job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb, - void *data, int flags, int sx, int sy) +job_run(const char *cmd, int argc, char **argv, struct session *s, + const char *cwd, job_update_cb updatecb, job_complete_cb completecb, + job_free_cb freecb, void *data, int flags, int sx, int sy) { - struct job *job; - struct environ *env; - pid_t pid; - int nullfd, out[2], master; - const char *home; - sigset_t set, oldset; - struct winsize ws; + struct job *job; + struct environ *env; + pid_t pid; + int nullfd, out[2], master; + const char *home; + sigset_t set, oldset; + struct winsize ws; + char **argvp; /* * Do not set TERM during .tmux.conf, it is nice to be able to use @@ -101,7 +102,13 @@ job_run(const char *cmd, struct session *s, const char *cwd, goto fail; pid = fork(); } - log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, cwd == NULL ? "" : cwd); + if (cmd == NULL) { + cmd_log_argv(argc, argv, "%s:", __func__); + log_debug("%s: cwd=%s", __func__, cwd == NULL ? "" : cwd); + } else { + log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, + cwd == NULL ? "" : cwd); + } switch (pid) { case -1: @@ -141,8 +148,14 @@ job_run(const char *cmd, struct session *s, const char *cwd, } closefrom(STDERR_FILENO + 1); - execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); - fatal("execl failed"); + if (cmd != NULL) { + execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); + fatal("execl failed"); + } else { + argvp = cmd_copy_argv(argc, argv); + execvp(argvp[0], argvp); + fatal("execvp failed"); + } } sigprocmask(SIG_SETMASK, &oldset, NULL); @@ -152,7 +165,10 @@ job_run(const char *cmd, struct session *s, const char *cwd, job->state = JOB_RUNNING; job->flags = flags; - job->cmd = xstrdup(cmd); + if (cmd != NULL) + job->cmd = xstrdup(cmd); + else + job->cmd = cmd_stringify_argv(argc, argv); job->pid = pid; job->status = 0; diff --git a/popup.c b/popup.c index 675bb2f9..d63ee996 100644 --- a/popup.c +++ b/popup.c @@ -32,13 +32,7 @@ struct popup_data { struct cmdq_item *item; int flags; - char **lines; - u_int nlines; - - char *cmd; - struct cmd_find_state fs; struct screen s; - struct job *job; struct input_ctx *ictx; int status; @@ -105,54 +99,11 @@ popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) ttyctx->arg = pd; } -static void -popup_write_screen(struct client *c, struct popup_data *pd) -{ - struct cmdq_item *item = pd->item; - struct screen_write_ctx ctx; - char *copy, *next, *loop, *tmp; - struct format_tree *ft; - u_int i, y; - - ft = format_create(c, item, FORMAT_NONE, 0); - if (cmd_find_valid_state(&pd->fs)) - format_defaults(ft, c, pd->fs.s, pd->fs.wl, pd->fs.wp); - else - format_defaults(ft, c, NULL, NULL, NULL); - - screen_write_start(&ctx, &pd->s); - screen_write_clearscreen(&ctx, 8); - - y = 0; - for (i = 0; i < pd->nlines; i++) { - if (y == pd->sy - 2) - break; - copy = next = xstrdup(pd->lines[i]); - while ((loop = strsep(&next, "\n")) != NULL) { - if (y == pd->sy - 2) - break; - tmp = format_expand(ft, loop); - screen_write_cursormove(&ctx, 0, y, 0); - format_draw(&ctx, &grid_default_cell, pd->sx - 2, tmp, - NULL); - free(tmp); - y++; - } - free(copy); - } - - format_free(ft); - screen_write_cursormove(&ctx, 0, y, 0); - screen_write_stop(&ctx); -} - static struct screen * popup_mode_cb(struct client *c, u_int *cx, u_int *cy) { struct popup_data *pd = c->overlay_data; - if (pd->ictx == NULL) - return (0); *cx = pd->px + 1 + pd->s.cx; *cy = pd->py + 1 + pd->s.cy; return (&pd->s); @@ -200,14 +151,12 @@ popup_free_cb(struct client *c) { struct popup_data *pd = c->overlay_data; struct cmdq_item *item = pd->item; - u_int i; if (pd->cb != NULL) pd->cb(pd->status, pd->arg); if (item != NULL) { - if (pd->ictx != NULL && - cmdq_get_client(item) != NULL && + if (cmdq_get_client(item) != NULL && cmdq_get_client(item)->session == NULL) cmdq_get_client(item)->retval = pd->status; cmdq_continue(item); @@ -216,15 +165,9 @@ popup_free_cb(struct client *c) if (pd->job != NULL) job_free(pd->job); - if (pd->ictx != NULL) - input_free(pd->ictx); - - for (i = 0; i < pd->nlines; i++) - free(pd->lines[i]); - free(pd->lines); + input_free(pd->ictx); screen_free(&pd->s); - free(pd->cmd); free(pd); } @@ -263,9 +206,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd, pd->sy = m->y - pd->py; screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0); - if (pd->ictx == NULL) - popup_write_screen(c, pd); - else if (pd->job != NULL) + if (pd->job != NULL) job_resize(pd->job, pd->sx - 2, pd->sy - 2); server_redraw_client(c); } @@ -276,13 +217,8 @@ popup_key_cb(struct client *c, struct key_event *event) { struct popup_data *pd = c->overlay_data; struct mouse_event *m = &event->m; - struct cmd_find_state *fs = &pd->fs; - struct format_tree *ft; - const char *cmd, *buf; + const char *buf; size_t len; - struct cmdq_state *state; - enum cmd_parse_status status; - char *error; if (KEYC_IS_MOUSE(event->key)) { if (pd->dragging != OFF) { @@ -314,13 +250,11 @@ popup_key_cb(struct client *c, struct key_event *event) } } - if (pd->ictx != NULL && (pd->flags & POPUP_WRITEKEYS)) { - if (((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0 || - pd->job == NULL) && - (event->key == '\033' || event->key == '\003')) - return (1); - if (pd->job == NULL) - return (0); + if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) || + pd->job == NULL) && + (event->key == '\033' || event->key == '\003')) + return (1); + if (pd->job != NULL) { if (KEYC_IS_MOUSE(event->key)) { /* Must be inside, checked already. */ if (!input_key_get_mouse(&pd->s, m, m->x - pd->px - 1, @@ -330,40 +264,8 @@ popup_key_cb(struct client *c, struct key_event *event) return (0); } input_key(&pd->s, job_get_event(pd->job), event->key); - return (0); } - - if (pd->cmd == NULL) - return (1); - - ft = format_create(NULL, pd->item, FORMAT_NONE, 0); - if (cmd_find_valid_state(fs)) - format_defaults(ft, c, fs->s, fs->wl, fs->wp); - else - format_defaults(ft, c, NULL, NULL, NULL); - format_add(ft, "popup_key", "%s", key_string_lookup_key(event->key, 0)); - if (KEYC_IS_MOUSE(event->key)) { - format_add(ft, "popup_mouse", "1"); - format_add(ft, "popup_mouse_x", "%u", m->x - pd->px); - format_add(ft, "popup_mouse_y", "%u", m->y - pd->py); - } - cmd = format_expand(ft, pd->cmd); - format_free(ft); - - if (pd->item != NULL) - event = cmdq_get_event(pd->item); - else - event = NULL; - state = cmdq_new_state(&pd->fs, event, 0); - - status = cmd_parse_and_append(cmd, NULL, c, state, &error); - if (status == CMD_PARSE_ERROR) { - cmdq_append(c, cmdq_get_error(error)); - free(error); - } - cmdq_free_state(state); - - return (1); + return (0); out: pd->lx = m->x; @@ -416,62 +318,12 @@ popup_job_complete_cb(struct job *job) server_client_clear_overlay(pd->c); } -u_int -popup_height(u_int nlines, const char **lines) -{ - char *copy, *next, *loop; - u_int i, height = 0; - - for (i = 0; i < nlines; i++) { - copy = next = xstrdup(lines[i]); - while ((loop = strsep(&next, "\n")) != NULL) - height++; - free(copy); - } - - return (height); -} - -u_int -popup_width(struct cmdq_item *item, u_int nlines, const char **lines, - struct client *c, struct cmd_find_state *fs) -{ - char *copy, *next, *loop, *tmp; - struct format_tree *ft; - u_int i, width = 0, tmpwidth; - - ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); - if (fs != NULL && cmd_find_valid_state(fs)) - format_defaults(ft, c, fs->s, fs->wl, fs->wp); - else - format_defaults(ft, c, NULL, NULL, NULL); - - for (i = 0; i < nlines; i++) { - copy = next = xstrdup(lines[i]); - while ((loop = strsep(&next, "\n")) != NULL) { - tmp = format_expand(ft, loop); - tmpwidth = format_width(tmp); - if (tmpwidth > width) - width = tmpwidth; - free(tmp); - } - free(copy); - } - - format_free(ft); - return (width); -} - int popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, - u_int sy, u_int nlines, const char **lines, const char *shellcmd, - const char *cmd, const char *cwd, struct client *c, - struct cmd_find_state *fs, popup_close_cb cb, void *arg) + u_int sy, const char *shellcmd, int argc, char **argv, const char *cwd, + struct client *c, struct session *s, popup_close_cb cb, void *arg) { struct popup_data *pd; - u_int i; - struct session *s; - int jobflags; if (sx < 3 || sy < 3) return (-1); @@ -489,39 +341,17 @@ popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, pd->arg = arg; pd->status = 128 + SIGHUP; - if (fs != NULL) - cmd_find_copy_state(&pd->fs, fs); screen_init(&pd->s, sx - 2, sy - 2, 0); - if (cmd != NULL) - pd->cmd = xstrdup(cmd); - pd->px = px; pd->py = py; pd->sx = sx; pd->sy = sy; - pd->nlines = nlines; - if (pd->nlines != 0) - pd->lines = xreallocarray(NULL, pd->nlines, sizeof *pd->lines); - - for (i = 0; i < pd->nlines; i++) - pd->lines[i] = xstrdup(lines[i]); - popup_write_screen(c, pd); - - if (shellcmd != NULL) { - if (fs != NULL) - s = fs->s; - else - s = NULL; - jobflags = JOB_NOWAIT|JOB_PTY; - if (flags & POPUP_WRITEKEYS) - jobflags |= JOB_KEEPWRITE; - pd->job = job_run(shellcmd, s, cwd, popup_job_update_cb, - popup_job_complete_cb, NULL, pd, jobflags, pd->sx - 2, - pd->sy - 2); - pd->ictx = input_init(NULL, job_get_event(pd->job)); - } + pd->job = job_run(shellcmd, argc, argv, s, cwd, + popup_job_update_cb, popup_job_complete_cb, NULL, pd, + JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, pd->sx - 2, pd->sy - 2); + pd->ictx = input_init(NULL, job_get_event(pd->job)); server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb, popup_draw_cb, popup_key_cb, popup_free_cb, pd); @@ -607,9 +437,8 @@ popup_editor(struct client *c, const char *buf, size_t len, py = (c->tty.sy / 2) - (sy / 2); xasprintf(&cmd, "%s %s", editor, path); - if (popup_display(POPUP_WRITEKEYS|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, - 0, NULL, cmd, NULL, _PATH_TMP, c, NULL, popup_editor_close_cb, - pe) != 0) { + if (popup_display(POPUP_CLOSEEXIT, NULL, px, py, sx, sy, cmd, 0, NULL, + _PATH_TMP, c, NULL, popup_editor_close_cb, pe) != 0) { popup_editor_free(pe); free(cmd); return (-1); diff --git a/spawn.c b/spawn.c index 41ffe612..9a801a38 100644 --- a/spawn.c +++ b/spawn.c @@ -265,8 +265,9 @@ 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 and store the working directory. + * 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"); diff --git a/tmux.1 b/tmux.1 index 4046dbf7..8b83732e 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4923,9 +4923,6 @@ The following variables are available, where appropriate: .It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane" .It Li "pane_width" Ta "" Ta "Width of pane" .It Li "pid" Ta "" Ta "Server PID" -.It Li "popup_key" Ta "" Ta "Key pressed in popup" -.It Li "popup_mouse_x" Ta "" Ta "Mouse X position in popup" -.It Li "popup_mouse_y" Ta "" Ta "Mouse Y position in popup" .It Li "rectangle_toggle" Ta "" Ta "1 if rectangle selection is activated" .It Li "scroll_position" Ta "" Ta "Scroll position in copy mode" .It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane" @@ -5584,58 +5581,24 @@ lists the format variables and their values. forwards any input read from stdin to the empty pane given by .Ar target-pane . .It Xo Ic display-popup -.Op Fl CEK +.Op Fl CE .Op Fl c Ar target-client .Op Fl d Ar start-directory .Op Fl h Ar height -.Op Fl R Ar shell-command .Op Fl t Ar target-pane .Op Fl w Ar width .Op Fl x Ar position .Op Fl y Ar position -.Op Ar command Ar line Ar ... +.Op Ar shell-command .Xc .D1 (alias: Ic popup ) -Display a popup on +Display a popup running +.Ar shell-command +on .Ar target-client . A popup is a rectangular box drawn over the top of any panes. Panes are not updated while a popup is present. -The popup content may be given in two ways: -.Bl -enum -offset Ds -.It -A set of lines as arguments. -Each line is a format which is expanded using -.Ar target-pane -as the target. -If a line contains newlines it is split into multiple lines. -Lines may use styles, see the -.Sx STYLES -section. -.It -A shell command given by -.Fl R -which is run and any output shown in the pane. -.El .Pp -The first argument, -.Ar command , -is a -.Nm -command which is run when a key is pressed. -The key is available in the -.Ql popup_key -format. -After -.Ar command -is run, the popup is closed. -It may be empty to discard any key presses. -If -.Fl K -is given together with -.Fl R , -key presses are instead passed to the -.Fl R -shell command. .Fl E closes the popup automatically when .Ar shell-command @@ -5645,14 +5608,6 @@ Two closes the popup only if .Ar shell-command exited with success. -With -.Fl K , -.Ql Escape -and -.Ql C-c -close the popup unless -.Fl E -is also given. .Pp .Fl x and @@ -5665,11 +5620,7 @@ and .Fl h give the width and height - both may be a percentage (followed by .Ql % ) . -If omitted, without -.Fl R -they are calculated from the given lines and with -.Fl R -they use half the terminal size. +If omitted, half of the terminal size is used. .Pp The .Fl C diff --git a/tmux.h b/tmux.h index fb15356a..d5e86e13 100644 --- a/tmux.h +++ b/tmux.h @@ -2067,9 +2067,9 @@ typedef void (*job_free_cb) (void *); #define JOB_NOWAIT 0x1 #define JOB_KEEPWRITE 0x2 #define JOB_PTY 0x4 -struct job *job_run(const char *, struct session *, const char *, - job_update_cb, job_complete_cb, job_free_cb, void *, int, - int, int); +struct job *job_run(const char *, int, char **, struct session *, + const char *, job_update_cb, job_complete_cb, job_free_cb, + void *, int, int, int); void job_free(struct job *); void job_resize(struct job *, u_int, u_int); void job_check_died(pid_t, int); @@ -3038,18 +3038,13 @@ int menu_display(struct menu *, int, struct cmdq_item *, u_int, menu_choice_cb, void *); /* popup.c */ -#define POPUP_WRITEKEYS 0x1 -#define POPUP_CLOSEEXIT 0x2 -#define POPUP_CLOSEEXITZERO 0x4 +#define POPUP_CLOSEEXIT 0x1 +#define POPUP_CLOSEEXITZERO 0x2 typedef void (*popup_close_cb)(int, void *); typedef void (*popup_finish_edit_cb)(char *, size_t, void *); -u_int popup_width(struct cmdq_item *, u_int, const char **, - struct client *, struct cmd_find_state *); -u_int popup_height(u_int, const char **); int popup_display(int, struct cmdq_item *, u_int, u_int, u_int, - u_int, u_int, const char **, const char *, const char *, - const char *, struct client *, struct cmd_find_state *, - popup_close_cb, void *); + u_int, const char *, int, char **, const char *, + struct client *, struct session *, popup_close_cb, void *); int popup_editor(struct client *, const char *, size_t, popup_finish_edit_cb, void *); diff --git a/window-copy.c b/window-copy.c index 531431c8..4558ed48 100644 --- a/window-copy.c +++ b/window-copy.c @@ -3963,8 +3963,8 @@ window_copy_pipe_run(struct window_mode_entry *wme, struct session *s, if (cmd == NULL || *cmd == '\0') cmd = options_get_string(global_options, "copy-command"); if (cmd != NULL && *cmd != '\0') { - job = job_run(cmd, s, NULL, NULL, NULL, NULL, NULL, JOB_NOWAIT, - -1, -1); + job = job_run(cmd, 0, NULL, s, NULL, NULL, NULL, NULL, NULL, + JOB_NOWAIT, -1, -1); bufferevent_write(job_get_event(job), buf, *len); } return (buf); From 81f9a23d25237f2b0c52a2867ddd2db59cc8f368 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 2 Mar 2021 11:00:38 +0000 Subject: [PATCH 89/90] Do not use NULL active window; also do not leak window name. GitHub issue 2590 from Chester Liu. --- names.c | 2 ++ spawn.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/names.c b/names.c index f437b53e..09b33082 100644 --- a/names.c +++ b/names.c @@ -109,6 +109,8 @@ default_window_name(struct window *w) { char *cmd, *s; + if (w->active == NULL) + return (xstrdup("")); cmd = cmd_stringify_argv(w->active->argc, w->active->argv); if (cmd != NULL && *cmd != '\0') s = parse_window_name(cmd); diff --git a/spawn.c b/spawn.c index 9a801a38..e3f8debe 100644 --- a/spawn.c +++ b/spawn.c @@ -184,7 +184,7 @@ spawn_window(struct spawn_context *sc, char **cause) NULL); options_set_number(w->options, "automatic-rename", 0); } else - w->name = xstrdup(default_window_name(w)); + w->name = default_window_name(w); } /* Switch to the new window if required. */ From 1466b570eedda0423d5a386d2b16b7ff0c0e477c Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 2 Mar 2021 12:05:15 +0000 Subject: [PATCH 90/90] Update CHANGES. --- CHANGES | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 8a438f81..a81f160c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,69 @@ -CHANGES FROM 3.2 TO 3.3 - -XXX - CHANGES FROM 3.1c TO 3.2 +* Improve performance of format evaluation. + +* Make jump command support UTF-8 in copy mode. + +* Support X11 colour names and other colour formats for OSC 10 and 11. + +* Add "pipe" variants of "copy-pipe" commands which do not copy. + +* Include "focused" in client flags. + +* Send Unicode directional isolate characters around horizontal pane borders if + the terminal supports UTF-8 and an extension terminfo(5) capability "Bidi" is + present. + +* Add a -S flag to new-window to make it select the existing window if one + with the given name already exists rather than failing with an error. + +* Addd a format modifier to check if a window or session name exists (N/w or + N/s). + +* Add compat clock_gettime for older macOS. + +* Add a no-detached choice to detach-on-destroy which detaches only if there + are no other detached sessions to switch to. + +* Add rectangle-on and rectangle-off copy mode commands. + +* Change so that window_flags escapes # automatically. A new format + window_raw_flags contains the old unescaped version. + +* Add -N flag to never start server even if command would normally do so. + +* With incremental search, start empty and only repeat the previous search if + the user tries to search again with an empty prompt. + +* Add a value for remain-on-exit that only keeps the pane if the program + failed. + +* Add a -C flag to run-shell to use a tmux command rather than a shell command. + +* Do not list user options with show-hooks. + +* Remove current match indicator in copy mode which can't work anymore since we + only search the visible region. + +* Make synchronize-panes a pane option and add -U flag to set-option to unset + an option on all panes. + +* Make replacement of ##s consistent when drawing formats, whether followed by + [ or not. Add a flag (e) to the q: format modifier to double up #s + +* Add -N flag to display-panes to ignore keys. + +* Change how escaping is processed for formats so that ## and # can be used in + styles. + +* Add a 'w' format modifier for string width. + +* Add support for Haiku. + +* Expand menu and popup -x and -y as formats. + +* Add numeric comparisons for formats. + * Fire focus events even when the pane is in a mode. * Add -O flag to display-menu to not automatically close when all mouse buttons @@ -271,9 +331,9 @@ CHANGES FROM 3.1c TO 3.2 * Change default position for display-menu -x and -y to centre rather than top left. -* Add support for per-client transient popups, similar to menus. These are - created with new command display-popup. Popups may either show fixed text and - trigger a tmux command when a key is pressed, or run a program (-R flag). +* Add support for per-client transient popups, similar to menus but which are + connected to an external command (like a pane). These are created with new + command display-popup. * Change double and triple click bindings so that only one is fired (previously double click was fired on the way to triple click). Also add default double